Image Gallery with Search & Virtualization
Challenge: "Design an image gallery application similar to Unsplash or Google Photos. It should support browsing, searching, and viewing thousands of images with excellent performance."
Requirements Gathering
Functional Requirements
- Browse images in a masonry/grid layout
- Search images by keyword with real-time suggestions
- Filter by category, orientation, color
- Lightbox view with zoom and navigation
- Infinite scroll pagination
- Responsive layout (1-4 columns based on viewport)
Non-Functional Requirements
- Initial load under 2s (LCP)
- Smooth scroll at 60fps with 10,000+ images loaded
- Works offline (recently viewed images cached)
- Accessible (keyboard navigation, screen reader support)
Architecture
âââââââââââââââââââââââââââââââââââââââââââ
â App Shell â
ââââââââââââââââŽâââââââââââââââââââââââââââĪ
â Search Bar â Filter Panel â
ââââââââââââââââīâââââââââââââââââââââââââââĪ
â Virtualized Image Grid â
â ââââââââ ââââââââ ââââââââ â
â â â â â â â â
â â img â â img â â img â â
â ââââââââ ââââââââ ââââââââ â
â ââââââââââââââ ââââââââ â
â â â â â â
â â img â â img â â
â ââââââââââââââ ââââââââ â
âââââââââââââââââââââââââââââââââââââââââââĪ
â Lightbox Overlay â
âââââââââââââââââââââââââââââââââââââââââââData Model
interface Image {
id: string;
url: string;
thumbnailUrl: string;
blurHash: string;
width: number;
height: number;
alt: string;
tags: string[];
color: string;
photographer: string;
}
interface GalleryState {
images: Image[];
query: string;
filters: { category?: string; orientation?: string; color?: string };
page: number;
hasMore: boolean;
loading: boolean;
selectedImageId: string | null;
}Key Design Decisions
1. Masonry Layout Strategy
Options:
- CSS Columns â Simple but reorders items top-to-bottom instead of left-to-right
- CSS Grid with
grid-row: span Nâ Requires knowing image heights upfront - JavaScript-computed positions â Full control, works with virtualization
Recommended: JS-computed positions with absolute positioning for virtualization compatibility.
2. Image Loading Pipeline
BlurHash placeholder â Low-res thumbnail â Full-res imageEach image transitions through three phases:
- BlurHash renders instantly from a 20-byte string (no network request)
- Thumbnail (200px wide) loads with
loading="lazy" - Full resolution loads only in lightbox view
3. Virtualization for Masonry
Standard virtualized lists assume fixed row heights. Masonry requires a custom virtualizer:
interface VirtualItem {
id: string;
top: number;
left: number;
width: number;
height: number;
}
function computeMasonryLayout(
images: Image[],
containerWidth: number,
columnCount: number,
gap: number,
): VirtualItem[] {
const columnWidth = (containerWidth - gap * (columnCount - 1)) / columnCount;
const columnHeights = new Array(columnCount).fill(0);
return images.map(img => {
const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
const aspectRatio = img.height / img.width;
const height = columnWidth * aspectRatio;
const top = columnHeights[shortestColumn];
const left = shortestColumn * (columnWidth + gap);
columnHeights[shortestColumn] = top + height + gap;
return { id: img.id, top, left, width: columnWidth, height };
});
}4. Search Architecture
- Debounced input (300ms)
- Cancel in-flight requests with AbortController
- Cache recent search results in memory (LRU cache)
- URL-synced query params for shareability
5. Caching Strategy
- Memory cache â Last 3 search result pages
- Cache API â Recently viewed full-res images (for lightbox revisits)
- Service Worker â Offline shell + thumbnail cache
Performance Optimizations
content-visibility: autoon off-screen sections to skip renderingfetchpriority="high"on first 4 visible images- Intersection Observer for lazy loading and infinite scroll
will-change: transformon scroll container for GPU compositing- Debounced resize handler recalculates masonry layout
- AVIF/WebP format negotiation via
<picture>element
Accessibility
- Images have descriptive
alttext - Lightbox traps focus, closes with Escape
- Grid navigable with arrow keys
- Search results announced with
aria-live="polite" - Reduced motion: disable transitions for
prefers-reduced-motion