Challenges📊 Data-IntensiveImage Gallery with Search & Virtualization
👑ApexSystem DesignPerformanceVirtualizationImages

Image Gallery with Search & Virtualization

Design an image gallery that handles thousands of images with infinite scroll, search, filtering, lazy loading, and masonry layout — a classic frontend system design challenge.

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 image

Each image transitions through three phases:

  1. BlurHash renders instantly from a 20-byte string (no network request)
  2. Thumbnail (200px wide) loads with loading="lazy"
  3. 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

  1. content-visibility: auto on off-screen sections to skip rendering
  2. fetchpriority="high" on first 4 visible images
  3. Intersection Observer for lazy loading and infinite scroll
  4. will-change: transform on scroll container for GPU compositing
  5. Debounced resize handler recalculates masonry layout
  6. AVIF/WebP format negotiation via <picture> element

Accessibility

  • Images have descriptive alt text
  • 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