In a CSS flex container with flex-wrap: wrap, how does the browser decide which items end up on each flex line, and does the gap property affect that decision?
answer
- collected greedily, in order
- hypothetical main size decides the break
- gap counts toward filling the line
- grow and shrink run afterwards, per line
- no gap at the start or end of a line
basics
~20 sItems are collected greedily in order: each item's hypothetical main size (its flex-basis clamped by min and max) plus the gap is added to the current line, and a new line starts as soon as the next item would not fit.
solid answer
~50 sLine collection happens before any flexing. The container computes each item's *hypothetical main size* — its `flex-basis` resolved and then clamped by the item's min and max sizing — and walks the items in order, adding them to the current line while their outer sizes plus the intervening `gap` still fit the container's inner main size. The first item that does not fit starts a new line, and an item that is wider than the line all by itself simply gets a line of its own. Only after the lines are fixed does the browser run the flexible-length algorithm, and it runs it **per line**: `flex-grow` and `flex-shrink` redistribute the free space of that one line. So flex-shrink never rescues an item from wrapping — the break point was already chosen. Gaps count toward filling a line, but no gap is added before the first or after the last item on a line.
code
css · 10 lines.gallery {
display: flex;
flex-wrap: wrap;
gap: 20px;
width: 500px;
}
.gallery > .card {
flex: 0 0 160px;
}go deeper
Know that wrap fills one line at a time, left to right, and starts a new line when the next item no longer fits, and that gap adds space between items on a line.
Explain the two phases clearly: collection uses hypothetical main sizes plus gaps, and only afterwards do flex-grow and flex-shrink resolve, separately for each line.
Demonstrate that you tune line density through flex-basis rather than breakpoints, and that you can debug a container that refuses to wrap because every item's basis resolves to zero.
Be able to argue where an intrinsically wrapping flex row is the right primitive at all, versus a layout whose alignment across rows is a stated requirement and therefore wants a two-dimensional system.
## Two separate phases A multi-line flex layout is easier to reason about once you see that it happens in two distinct phases: 1. **Collect items into flex lines.** This decides *where the breaks are*. 2. **Resolve flexible lengths, then align.** This decides *how big each item is* and where it sits. Almost every confusing multi-line result comes from assuming these happen together, or in the other order. ## Phase one: the hypothetical main size Before anything is collected, each item gets a **hypothetical main size**. That is the item's `flex-basis` — resolved against the container, falling back to `width`/`height` and then to content size when `flex-basis` is `auto` — and then clamped by the item's own `min-width`/`max-width` (or the block-axis equivalents for a column container). Add the item's own margins, borders and padding and you have its *outer* hypothetical main size: the amount of room it is asking for before any flexing. Note what is **not** consulted here: `flex-grow` and `flex-shrink`. They are numbers used to distribute space later; they play no part in choosing where lines break. ## Phase one: greedy collection The container then walks its items in order-modified document order and fills one line at a time. It keeps adding items while the running total — outer hypothetical main sizes, plus one `gap` between each adjacent pair — is less than or equal to the container's inner main size. The first item that would push the total over starts a fresh line. This is a plain greedy pass, not a balancing pass. The browser never tries to even out the number of items across lines, and it never looks ahead to produce a prettier distribution. If you have four items that fit three-per-line, you get three and then one — not two and two. Two details fall out of this: - An item whose own hypothetical size exceeds the container's main size is placed alone on its line and then shrinks (or overflows) there. - A `nowrap` container skips the whole pass and puts everything on a single line by definition. ## Where gap fits in `gap` (and its longhands `row-gap` and `column-gap`) creates fixed space *between* flex items and between flex lines. In the main axis it counts toward line filling exactly like an item's size does, so raising `gap` can push an item onto the next line without any item changing size. ```css .cards { display: flex; flex-wrap: wrap; gap: 20px; /* 20px between items and between lines */ width: 500px; } .cards > .card { flex: 0 0 160px; } /* 160 + 20 + 160 = 340 fits; adding 20 + 160 = 520 does not. Result: two items on line one, one item on line two. */ ``` Crucially, gap is **not** added before the first item or after the last item on a line, and not above the first line or below the last. That is the main reason `gap` replaced the old negative-margin wrapper hacks: the spacing is interior only, so it never fights the container's padding. ## Phase two: flexing happens per line Once the lines exist, each line is treated as its own little single-line flex context. The free space of *that line* is measured, and `flex-grow` distributes positive free space or `flex-shrink` removes negative free space among the items on *that line only*. Two lines with different item counts therefore end up with differently sized items — that is expected behaviour, not a bug. Alignment follows the same split. `justify-content` distributes leftover main-axis space within each line separately. Each line's cross size is determined by the tallest (or, in a column container, widest) item on it, and the set of lines is then positioned along the container's cross axis by `align-content`. ## Interview-grade consequences - **`flex-shrink` cannot prevent a wrap.** People often try `flex-shrink: 1` on a wrapping container hoping items will squeeze together instead of breaking. The break was chosen from hypothetical sizes before shrinking ever ran. - **`flex-basis` is your line-density dial.** Since collection is driven by hypothetical main sizes, changing `flex-basis` (say from `240px` to `180px`) is what changes how many cards fit per line — often with no media query at all. - **`flex: 1` on wrapping cards is a trap.** A `flex-basis` of `0` makes every item's hypothetical size tiny, so they all collect onto one line and then grow. If you want wrapping, give the basis a real width, e.g. `flex: 1 1 240px`. - **Percentages plus gap overflow.** Three items at `flex-basis: 33.333%` with a `gap` cannot fit on one line, because the gap is added on top of 100%. Either drop to a smaller percentage or, more robustly, use a `calc()` basis that subtracts the gap.
- Why does flex: 1 on every child of a wrapping container often produce a single line instead of a wrapped grid of cards?`flex: 1` expands to `flex: 1 1 0%`, so every item's hypothetical main size is effectively zero. Collection sees items that ask for no space, packs them all onto one line, and only then grows them to fill it. Give the basis a real width — `flex: 1 1 240px` — so the collection pass has something to measure.
- Three items each with flex-basis: 33.333% and gap: 16px overflow their container. Why?The percentages already claim the whole inner width, and gap is added on top of them, so the line needs 100% plus 32px. Line collection measures sizes plus gaps together. Either shrink the basis or compute it with `calc((100% - 32px) / 3)` so the gaps are subtracted from the share each item asks for.
- If two flex lines contain different numbers of items, will the items be the same width?Not unless you stop them from growing. Flexible lengths are resolved per line, so each line distributes its own free space among its own items. A line with two items hands each of them far more growth than a line with four. Setting `flex-grow: 0`, or a `max-width`, keeps widths consistent across lines.
saying these in an interview costs you the question
- Thinks the browser balances items evenly across lines
- Believes flex-shrink runs first to avoid a wrap
- Assumes gap is ignored when measuring whether an item fits
- Thinks flex-grow influences where lines break
- Expects gap to also add space outside the first and last items