How would you build a responsive grid without media queries using auto-fit/auto-fill and minmax()?

Advanced15 min interview
Skills tested:
Writing the repeat(auto-fit, minmax()) pattern correctlyExplaining the difference between auto-fit and auto-fill preciselyPreventing narrow-viewport overflow with min()Choosing between the two keywords based on desired behaviourKnowing why this is not a substitute for container queries

Advertisement

🧩 Scenario

This single declaration replaces the three or four breakpoints that a card gallery used to need. Instead of declaring four columns above 1200px, three above 900px, two above 600px, and one below, you state a minimum comfortable card width and let the browser compute the count. The layout then responds continuously rather than in jumps, and it keeps working inside a narrowed sidebar or a resized panel that no viewport breakpoint would have accounted for.

Architecture Walkthrough

The Pattern

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

That is the whole thing. It reads as: create as many columns as fit, where each column is at least 240px and otherwise shares the free space equally.

The browser computes the count by dividing the container's available inline size, minus gaps, by the minimum track size and taking the floor. In a 1000px container with a 16px gap and a 240px minimum, four tracks would need 960px of track plus 48px of gap, which exceeds 1000px, so three tracks are created. Each then expands via the 1fr maximum to consume the leftover space, giving roughly 322px per card.

Resize the container and the count recomputes automatically. There are no breakpoints to maintain, and the behaviour is continuous rather than stepped.

auto-fit Versus auto-fill

Both keywords create as many tracks as fit. The difference is what happens to tracks that end up empty.

auto-fill keeps empty tracks in the grid. They occupy their minimum width, so the items that do exist stay at their minimum size and the row has visible empty space on the right. With four items, a 200px minimum, and a 1000px container that fits five tracks, the four items are each roughly 200px and the fifth track sits empty.

auto-fit collapses empty tracks to zero width and then lets the remaining tracks absorb all the freed space via their 1fr maximum. In the same scenario, the four items each stretch to roughly 250px and there is no visible gap at the end of the row.

The important nuance is that auto-fit does not delete the tracks; it collapses them. They still exist in the line-numbering scheme, which matters if you are placing items with explicit line numbers in the same grid.

Which one you want depends on intent. auto-fit is right when items should fill the available width, which is the usual case for card galleries and dashboards. auto-fill is right when items must keep a consistent size regardless of how many there are, such as a product grid where a single remaining item should not stretch to the full container width, or a calendar-like layout where cell size carries meaning.

Preventing Overflow on Narrow Viewports

The pattern has one real failure mode. minmax(360px, 1fr) in a 320px container produces a single 360px track, which overflows. The minimum is a hard floor, and there is nothing in the declaration to cap it at the container width.

The fix is to make the minimum itself responsive:

grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));

min(360px, 100%) evaluates to 360px when there is room and to the container width when there is not, so the track can never exceed its container. This is the idiomatic form of the pattern for production and is worth writing by default rather than adding after the bug appears.

A related refinement uses clamp() for the minimum when the comfortable card width itself should scale with the viewport.

What This Pattern Does Not Solve

It responds to the grid container's width, not to the viewport, which is a genuine advantage: the same component works in a full-width page, a narrow sidebar, and a modal. But it only adjusts the track count. Everything else about the card, its font size, padding, internal layout, or whether the image sits above or beside the text, still needs either media queries or, better, container queries.

So the honest framing is that auto-fit plus minmax() removes breakpoints for track counting specifically. Container queries are the tool for changing a component's internal layout based on its own width, and the two compose well: auto-fit for the gallery, a container query inside each card for its internal arrangement.


Key Code Explained

/* THE PATTERN: as many columns as fit, min 240px, share the rest */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
/* 1000px container, 16px gap, 240px min:
   4 tracks would need 960 + 48 = 1008px > 1000 -> 3 tracks
   each track then grows via 1fr to ~322px */

/* auto-fill KEEPS empty tracks: items stay at their minimum */
.auto-fill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0;
}
/* 4 items, 1000px container (fits 5 tracks):
   items are ~200px each, one empty 200px track remains */

/* auto-fit COLLAPSES empty tracks to zero: items stretch */
.auto-fit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0;
}
/* Same 4 items, same container: items are ~250px each, no trailing gap */

/* THE OVERFLOW BUG: a 360px minimum in a 320px viewport */
.overflows {
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
}

/* THE FIX: cap the minimum at the container width */
.production-ready {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: 16px;
}

/* Scale the minimum itself with the viewport */
.fluid-min {
  grid-template-columns: repeat(auto-fit, minmax(clamp(200px, 30vw, 320px), 1fr));
}

/* Uniform row heights alongside the responsive columns */
.uniform {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  grid-auto-rows: minmax(180px, auto); /* implicit rows need their own sizing */
  gap: 16px;
}

/* Compose with container queries: auto-fit counts, CQ restyles */
.card {
  container-type: inline-size;
}
@container (min-width: 320px) {
  .card__body {
    display: flex; /* image beside text once the CARD is wide enough */
    gap: 12px;
  }
}

The .auto-fill-grid versus .auto-fit-grid pair is the answer to the most commonly asked half of this question. The two declarations differ by one keyword and produce visibly different results only when there are fewer items than tracks, which is exactly the case people forget to test. Describing auto-fit as collapsing empty tracks to zero, rather than removing them, is the precise version of the answer.

The .production-ready rule is the one to write habitually. Without min(360px, 100%), the pattern is correct at every width except the narrow ones that matter most, and the resulting horizontal scroll on a phone is a bug that a desktop review will never catch.


Tradeoffs

Behaviourauto-fillauto-fit
Track countAs many as fitAs many as fit
Empty tracksKept at minimum widthCollapsed to zero width
Items with few itemsStay at minimum sizeStretch to fill the row
Trailing empty spaceVisibleNone
Line numbersInclude empty tracksStill include collapsed tracks
Best forConsistent item size, product grids, calendarsFilling available width, card galleries

What Interviewers Actually Check

  • Whether you can write the pattern correctly from memory
  • Whether you can explain the auto-fit versus auto-fill difference precisely, including that tracks collapse rather than disappear
  • Whether you know the narrow-viewport overflow failure and the min() fix
  • Whether you can pick the right keyword for a given design intent
  • Whether you position this as solving track counting rather than all responsive needs

Follow-Up Questions

  1. How does gap participate in the track count calculation, and does a larger gap reduce the number of columns?
  2. What happens to explicitly line-placed items in an auto-fit grid where some tracks have collapsed?
  3. How would you keep the last row's items left-aligned at their minimum width instead of stretching?
  4. Where do container queries take over from this pattern, and how do the two compose in a card gallery?
  5. Can the same technique be applied to rows with grid-template-rows, and why is it far less useful there?

Common Candidate Mistakes

  • Using auto-fit and auto-fill as if they were synonyms, which produces the wrong result specifically when there are fewer items than available tracks
  • Describing auto-fit as removing empty tracks when it collapses them to zero width, a distinction that matters for line-based placement in the same grid
  • Shipping minmax(360px, 1fr) with no cap, which overflows any container narrower than the minimum and produces horizontal scroll on phones
  • Expecting the pattern to respond to the component's own width for anything other than track count, when it is the grid container's width that drives the calculation
  • Forgetting that implicit rows in such a grid are sized by grid-auto-rows, so uniform row heights need their own declaration

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you write the responsive grid pattern from memory?
  • Can you explain how the browser computes the track count, including gaps?
  • Can you state precisely what auto-fit does to empty tracks?
  • Can you prevent overflow on narrow viewports without a media query?
  • Can you say when auto-fill is the correct choice over auto-fit?

Summary

grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) produces a responsive grid with no media queries. The browser divides the container's available inline size, minus gaps, by the minimum track size to decide how many tracks fit, and the 1fr maximum then lets those tracks absorb the leftover space. The result adapts continuously as the container resizes, and because it responds to the container rather than the viewport, the same component works in a full-width page, a sidebar, or a modal.

auto-fit and auto-fill both create as many tracks as fit; they differ in what happens to empty ones. auto-fill keeps empty tracks at their minimum width, so existing items stay at their minimum size and trailing space is visible. auto-fit collapses empty tracks to zero width so the remaining items stretch to fill the row. Collapsed is not the same as removed, since the tracks still occupy line numbers. Use auto-fit when items should fill the width and auto-fill when item size must stay consistent regardless of count.

The pattern's one real failure is a fixed minimum larger than the container, which overflows narrow viewports. minmax(min(240px, 100%), 1fr) caps the minimum at the container width and should be the default form. Finally, this technique removes breakpoints for track counting only: changing a card's internal layout based on its own width is what container queries are for, and the two compose naturally with auto-fit on the gallery and a container query inside each card.

Frequently Asked Questions

What is the actual difference between auto-fit and auto-fill?

Both create as many tracks as fit. auto-fill keeps the empty ones, so items stay at their minimum width; auto-fit collapses the empty ones to zero, so the remaining items stretch to fill the row.

Why does the grid overflow on very narrow screens?

Because the minmax minimum is a fixed length larger than the container. min(240px, 100%) as the minimum caps it at the container width and prevents the overflow.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement