skip to content

Across a large site, sizes values on <img> elements drift out of sync with the CSS that actually lays those images out. How would you keep responsive-image markup honest at that scale, and when would you decide an image should not carry a srcset at all?

level: principalimportance: nice to knowfreq 30%

answer

  1. duplicated fact, no binding
  2. a property of the slot, not the image
  3. one source of truth for both copies
  4. assert a bound, never a filename
  5. know when to opt out

basics

~20 s

Treat sizes as duplicated layout knowledge: generate it from the same breakpoint and container tokens the CSS uses, own it per layout slot in a component rather than per image, and check it automatically. Skip srcset where the rendered box is fixed or the image is tiny.

solid answer

~60 s

The drift is structural, not sloppiness: the column width lives in the stylesheet and again in an HTML attribute, with nothing binding them. So bind them. Define a small set of named layout slots — hero, card, article figure, avatar — and let one component per slot emit both the `sizes` string and the candidate ladder, derived from the same breakpoint and container-width tokens the CSS consumes. That turns hundreds of hand-written attributes into a handful of reviewed ones. Back it with an automated check that loads representative pages at a few viewports and densities and flags any image whose chosen file is far wider than its rendered box times the density — a bound, not a filename, since selection is a hint. And be willing to opt out: an image with a fixed rendered box wants x descriptors, an icon or a small decorative asset wants a single file or inline SVG, and a slot whose width nobody can state confidently is a design problem to settle before it becomes an attribute.

code

javascript · 8 lines
javascript
const report = [...document.images].map(img => {
  const box = img.getBoundingClientRect().width;
  return {
    url: img.currentSrc,
    ratio: box ? img.naturalWidth / (box * devicePixelRatio) : null
  };
});
console.table(report.filter(r => r.ratio && (r.ratio > 2 || r.ratio < 0.9)));

go deeper

for a junior

Recall that a sizes value only stays correct while the CSS it describes stays the same, so a layout change means the image markup has to be revisited too.

for a middle

Explain why the value belongs to a layout slot rather than an individual image, and how putting it in a shared component reduces the number of places it can be wrong.

for a senior

Show how you would detect the drift on real pages — a rendered check comparing chosen file width against box width and density — and why that check asserts a bound rather than a filename.

for a principal

Own the whole tradeoff: how many derivatives to generate and cache per class of image, where the single source of truth for slot widths lives, and which images should opt out of responsive machinery entirely.

## Name the real problem The `sizes` attribute is a *second copy* of a layout fact. The first copy lives in the stylesheet — grid definitions, container max-widths, gutters, breakpoints. The second lives in an HTML attribute the browser reads before it has seen any of that. There is no mechanism connecting them, so any redesign silently invalidates the copy nobody re-reads. At ten images a person can hold both in their head. At ten thousand, drift is certain. A principal-level answer therefore does not say "be careful with `sizes`". It removes the opportunity to be careless. ## Move from per-image to per-slot The key reframing: `sizes` is not a property of an image, it is a property of a **layout slot**. Every image in the product grid shares one `sizes` value; every article figure shares another. Enumerate the slots — typically far fewer than people expect, often five to ten for a whole site — and give each one an owner: - a component or template partial per slot that emits the `<img>`; - the `sizes` string written once inside it; - the candidate widths derived from the widths that slot can actually take. Authors then choose a slot, not an attribute. The number of hand-written `sizes` values in the codebase drops to the number of slots, and each becomes worth reviewing carefully. ## Derive from shared tokens Go one step further where the toolchain allows: generate both halves from the same source of truth the CSS uses. If breakpoints and container widths are tokens, the `sizes` string for the card slot is a function of them, and the derivative widths to build are a function of the slot's widths crossed with the densities you support (typically 1x and 2x, occasionally 3x), deduplicated and spaced by a roughly constant factor so the byte steps are even. A layout change then moves the tokens and both copies follow. Where generation is impractical, at minimum put the two next to each other in review: a change to grid columns or a container max-width should show the `sizes` change in the same diff, and reviewers should be trained to ask for it. ## Verify with a bound, not an assertion Static review cannot catch drift because the attribute stays syntactically valid while becoming false. The check has to run against a rendered page: ```js const offenders = [...document.images] .map(img => ({ url: img.currentSrc, ratio: img.naturalWidth / (img.getBoundingClientRect().width * devicePixelRatio) })) .filter(row => row.ratio > 2); ``` Run it over representative pages at a handful of viewport-and-density combinations and fail on offenders. Two design decisions matter here. First, assert a **bound**, never a specific candidate URL — the standard permits the user agent to choose differently, and a filename assertion will flake across browsers. Second, watch the *low* side as well: a ratio well below 1 means the ladder has no candidate large enough and users are seeing an upscaled image, which is the more damaging failure. ## Know when not to use srcset Responsive images are not free — they multiply build artefacts, storage, cache entries and a per-image attribute that can be wrong. Reach for the simpler thing when it applies: - **Fixed rendered box** (logo, avatar, badge): x descriptors. There is no width to compute, so there is no `sizes` to drift. - **Small assets**: below a few tens of kilobytes the savings are noise against the cost of extra derivatives and cache entries; ship one file. - **Vector artwork**: an SVG is resolution-independent, so the whole question dissolves. - **Long-tail, user-uploaded, rarely-viewed images**: a single sensible derivative is often the right economics. - **A slot nobody can state a width for**: that is unresolved design, not a markup problem. Settle the layout first; an attribute cannot express a width the team has not decided. ## What you are trading More candidates per image means smaller downloads and more build time, more storage, more cache fragmentation across CDN variants, and more surface for the ladder to be wrong. Fewer means simpler operations and some wasted bytes. The judgment is where on that curve a given class of image sits — a hero on a landing page earns a full ladder; a moderation-queue thumbnail does not. Making that call *per slot*, once, is the leverage; making it per image, repeatedly, is how the drift started. ## How to present this in an interview Lead with the diagnosis — duplicated layout knowledge with no binding — then the three moves: consolidate to slots, derive from shared tokens, verify with a rendered bound. Finish with the opt-out cases, because a candidate who knows when the machinery is not worth it is more convincing than one who applies it everywhere.

  • What is the strongest argument against generating sizes automatically from layout tokens?
    Generation only works where the slot's width is genuinely a function of the tokens. Editorial layouts, content-driven grids and third-party embeds can produce widths no token predicts, and a generator that guesses confidently is worse than a human writing one conservative value. The honest design is generation for the slots that are regular, plus a reviewed manual escape hatch for the ones that are not.
  • How many candidate widths per image is the right number?
    Enough that consecutive candidates differ meaningfully in bytes, and no more. Since weight tracks area, widths spaced by a factor of about 1.4 to 1.5 give roughly even steps, and anchoring on the widths the slots actually request at 1x and 2x usually yields four to six per image. More than that multiplies build artefacts and cache entries for differences users cannot see.
  • You inherit a site where sizes is wrong nearly everywhere. What do you fix first?
    Measure before you touch anything: run the rendered-page check across representative templates and rank images by how oversized the chosen candidate is and how often the template is viewed. Fix the worst slots by moving them behind a component, since that both corrects the value and prevents recurrence. Chasing individual `<img>` tags produces the same mess again after the next redesign.

saying these in an interview costs you the question

  • Treats sizes as a per-image detail rather than a slot property
  • Answers only "review it more carefully" with no mechanism
  • Asserts an exact candidate filename in automated tests
  • Applies srcset to icons and logos with fixed rendered sizes
  • Ignores the upscaled case and watches only for over-fetching

context