The Pinterest masonry grid is harder than it looks. Variable-height columns, infinite scroll, lazy-loaded images, no layout shift — all on a feed that may have thousands of items. The interview tests whether you understand the gnarly intersection of CSS layout, image loading, and virtualization.
Functional requirements
- Multi-column masonry layout with variable heights
- Infinite scroll with pagination
- Lazy-loaded images
- Click an image to open detail view
- Responsive: column count changes with viewport width
Architecture choices
CSS Grid vs JS layout
CSS Grid with grid-template-columns: repeat(auto-fill, minmax(...)) is the simplest. But: CSS Grid does not produce true masonry — items in the same row align by the tallest. The result is awkward gaps.
True masonry requires either:
- CSS
grid-template-rows: masonry: the new spec, but Safari-only as of 2026. Not production-ready. - JS-based layout: compute item positions manually, position absolutely. Industry standard.
JS layout: column algorithm
Maintain N columns (number based on viewport width). For each new item:
- Find the shortest column
- Place the item at the bottom of that column
- Update that column’s running height
Each item gets position: absolute; left: ... ; top: ... — the container has explicit height = max column height.
Virtualization
Off-screen items are not rendered. Use intersection observer to determine which range of items is visible (with overscan). Maintain placeholder space for unrendered items so the scrollbar accuracy is preserved.
Library: react-virtualized supports masonry. TanStack Virtual is more flexible but masonry support is custom.
Image lazy loading
- Native
loading="lazy"handles most cases — well-supported, zero JS - For finer control (preload near-viewport images, fade-in animation), use IntersectionObserver
- Always provide explicit
widthandheightattributes to reserve space
Preventing CLS (Cumulative Layout Shift)
Pinterest is famous for being a CLS minefield. Without care, every image load shifts items below.
Mitigations:
- Server returns image dimensions with the manifest (width, height per pin)
- Client reserves the exact box before the image loads
- Use
aspect-ratioCSS property where supported - Background placeholder (low-res blurred preview) renders while full-res loads
Detail view
Tap on a pin → modal or new route. Modal preserves scroll position; new route requires explicit “back” handling. For Pinterest specifically, use a modal that overlays without unmounting the grid.
Resize handling
When viewport width changes:
- Recompute number of columns
- Re-layout all items
- Use ResizeObserver, debounce 100–300ms
Layout recomputation can be expensive — keep heights cached per item to avoid re-measuring.
Performance budget
- First Contentful Paint < 1.5s
- Largest Contentful Paint < 2.5s
- CLS < 0.05
- Time to Interactive < 3.5s
Frequently Asked Questions
Why not use the native CSS masonry spec?
It is in Safari and behind a flag in Chromium as of 2026. Production code still uses JS layout for cross-browser support.
How do you handle a pin that loads as a different size than expected?
Re-layout from that pin downward. Bound the visible items to avoid jarring scroll jumps. Most apps update only after the user scrolls past the affected pin.
How does Pinterest handle very long pins?
Pin heights are server-capped at a max ratio (e.g., 1:3.5). Anything longer is rendered with a “view full” CTA.