Layout engines
Three pure, O(n) algorithms. Each visits every image once and returns absolute positions.
Masonry (Pinterest)
Pinterest content is visually heterogeneous — every pin has a different aspect ratio. Masonry preserves each ratio and packs tightly by dropping the next image into the shortest column.
for (const image of images) {
const renderHeight = (image.height / image.width) * columnWidth;
const c = shortestColumn(heights); // scan of ≤ 6 columns → O(1)
place(image, x = c * (colW + gap), y = heights[c]);
heights[c] += renderHeight + gap;
}
containerHeight = max(heights) - gap;Incremental append. Infinite scroll continues from the prior column-height vector, so new
pages cost O(delta) and existing tiles never move:
const state = computeMasonryState(firstPage, width, { columns, gap });
const next = appendMasonry(state, newImages); // O(newImages)Column count is responsive: 2 / 3 / 4 / 6 at 640 / 1024 / 1536px.
Justified (Airbnb gallery)
Greedy row filling, then every image in a row is scaled to a shared height so the row width exactly equals the container — no gaps, no cropping, ratios preserved.
// For a row of k images with aspect ratios arᵢ:
rowHeight = (containerWidth - gap * (k - 1)) / Σ arᵢ;
widthᵢ = arᵢ * rowHeight; // Σ widthᵢ + gaps === containerWidthA row closes once scaling it to fill the width brings it to or under the target row height. The trailing row is filled only if it's close to target; otherwise it's left-aligned to avoid grotesquely large images.
Fixed grid (Airbnb listings)
Uniform aspect-ratio cells in perfectly aligned rows — the eye can compare listings. The cell ratio adapts by breakpoint:
| Breakpoint | Columns | Cell aspect |
|---|---|---|
| mobile | 1 | 1:1 |
| tablet | 2 | 4:3 |
| desktop | 3 | 3:2 |
| ultrawide | 4 | 16:9 |
Complexity
| Engine | Time | Memory |
|---|---|---|
| Masonry | O(n) · O(delta) append | O(n) positions |
| Justified | O(n) | O(n) positions |
| Fixed grid | O(n) | O(n) positions |