skip to content

Given <img srcset="p-400.jpg 400w, p-800.jpg 800w, p-1600.jpg 1600w" sizes="400px">, how does a browser combine sizes with the device pixel ratio to choose a candidate, and can you rely on it always picking the same one?

level: middleimportance: should knowfreq 48%

answer

  1. resolve sizes, then divide
  2. needed pixels ≈ sizes × DPR
  3. effective density per candidate
  4. the browser may overrule you
  5. cached larger file tends to stay

basics

~20 s

The browser resolves sizes to CSS pixels and multiplies by the screen's device pixel ratio to get the image pixels it needs — 400px at 2x asks for about 800 — then takes a candidate that meets it. Selection is a hint, so the exact file is not guaranteed.

solid answer

~50 s

The browser first resolves `sizes` to a length in CSS pixels — here a flat `400px`. It then divides each candidate's w descriptor by that length to get an effective density: `p-400.jpg` is 1x for this slot, `p-800.jpg` is 2x, `p-1600.jpg` is 4x. It picks a candidate whose effective density meets the screen's device pixel ratio, so a 1x screen takes `p-400.jpg` and a 2x screen takes `p-800.jpg`. The shorthand is: needed image pixels ≈ sizes × DPR. Crucially the specification lets the user agent choose *any* candidate — it may consider what is already in the cache, and browsers commonly keep a larger candidate they have already downloaded rather than fetching a smaller one when the viewport shrinks. So treat `srcset` as a well-informed hint. Your obligation is an honest `sizes` and a sensible ladder of widths; the final pick is the browser's.

code

html · 5 lines
html
<img src="p-800.jpg"
     srcset="p-400.jpg 400w, p-800.jpg 800w, p-1600.jpg 1600w"
     sizes="400px"
     width="400" height="300"
     alt="Product photo">

go deeper

for a junior

Recall the shorthand that the pixels needed are roughly the rendered width times the screen's device pixel ratio, so a 400px slot on a 2x screen wants an 800px file.

for a middle

Walk the two steps out loud: resolve sizes to CSS pixels, then convert each candidate into an effective density and compare it with the device pixel ratio.

for a senior

Show judgment about the candidate ladder — spacing by a constant factor, anchoring on real layout widths, and knowing that selection is a hint you cannot assert exactly in a test.

for a principal

Own how many derivatives per image an organisation should generate and cache, weighing the byte curve against build time, storage and cache fragmentation.

## The arithmetic Selection with w descriptors is a two-step calculation. **Step 1 — resolve `sizes` to a source size in CSS pixels.** Media conditions are evaluated against the current viewport, first match wins. In the example `sizes="400px"` there are no conditions, so the source size is 400 CSS pixels. **Step 2 — turn each candidate into an effective density and compare with the screen.** Divide the w descriptor by the source size: ```text source size = 400 CSS px p-400.jpg 400 / 400 = 1x effective p-800.jpg 800 / 400 = 2x effective p-1600.jpg 1600 / 400 = 4x effective ``` Now compare against the device pixel ratio. On a 1x display the browser wants an effective density of at least 1 and takes `p-400.jpg`. On a 2x display it wants at least 2 and takes `p-800.jpg`. On a 3x phone nothing sits exactly at 3, so it goes up to `p-1600.jpg` rather than serve something soft. The mental shortcut engineers actually use is the product form: **image pixels needed ≈ sizes × DPR**. A 400 CSS-pixel slot on a 2x screen needs roughly an 800-pixel file. This is also why a `sizes` value that is too large is expensive in a compounding way — the error is multiplied by the density. ## Why the same markup gives different results on different machines The inputs are the viewport (through the media conditions), the device pixel ratio, and the candidate ladder. Change any one and the choice changes. That is the entire point: one markup fragment, many correct outcomes. It also means "which file does this page load?" has no single answer, and testing on your own laptop tells you about exactly one combination. Browser zoom folds into this too, because zoom changes the effective device pixel ratio — zooming in can push the browser onto a larger candidate. ## Selection is permitted to deviate The HTML standard does not force the user agent's hand. It may take into account factors of its own and choose a different candidate than the arithmetic suggests. Two consequences you should be able to state: 1. **The cache wins.** If a larger candidate for the same `<img>` has already been downloaded, browsers generally keep using it rather than fetching a smaller one. Shrinking the window therefore usually triggers no new request — the switch is effectively one-way, upward. 2. **You cannot assert an exact file in a test.** Asserting "on this viewport the network shows `p-800.jpg`" is brittle across browsers and machines. Assert the *property* instead: the requested candidate is not wildly larger than the rendered box times the DPR. ## Choosing the ladder of widths Given the arithmetic, the useful spacing between candidates is in bytes, not in round numbers. File weight grows roughly with area, so widths spaced by a constant factor — say each about 1.4 to 1.5 times the last — give roughly even byte steps. Candidates 20 pixels apart create build and cache work for a difference nobody can see. Anchor the ladder on the widths your layout actually produces: the slot width at each breakpoint, each multiplied by 1x and 2x, then deduplicated. Going above roughly 3x is usually wasted: on very dense screens the extra pixels are past the point of visible improvement, and the byte cost is quadratic. ## Where people go wrong - **Reading w descriptors as breakpoints.** `800w` never means "switch here"; the switch point is derived from `sizes` and DPR. - **Ignoring density.** Concluding that a 400-pixel slot only ever needs a 400-pixel file, which leaves every high-density screen soft. - **Expecting a downgrade on resize.** Making the window narrower will not usually fetch a smaller file, so a "savings" demo that resizes the window proves nothing. - **Testing at one DPR.** Emulating device pixel ratio in developer tools is the only way to see the other half of the matrix. ## Verifying a real page Read `currentSrc` on the element to see the URL that was actually chosen, and compare the chosen file's width against the element's rendered width times `devicePixelRatio`. A ratio near 1 means the ladder and `sizes` agree with reality; a ratio of 3 or 4 means `sizes` is over-stated or the ladder has a gap.

  • If a user makes the window narrower, will the browser download a smaller candidate?
    Usually not. The specification lets the user agent consider what it already holds, and browsers in practice keep a larger candidate they have downloaded rather than re-fetching a smaller one — the switch is effectively one-way, upward. This is why demonstrating savings by resizing a window proves nothing; you have to reload at the target viewport.
  • How would you choose the widths in the ladder rather than picking round numbers?
    Start from the widths the layout actually produces — the slot width at each breakpoint — and multiply each by 1x and 2x, then deduplicate. Space the survivors by a roughly constant factor, around 1.4 to 1.5, so the byte steps are even, since weight grows with area. Candidates a few dozen pixels apart cost build and cache work for an invisible difference.
  • Does browser zoom affect which candidate is chosen?
    Yes. Zoom changes the effective device pixel ratio, and the ratio is one of the two inputs to the selection, so zooming in can push the browser onto a larger candidate on the next load. It is another reason a single manual check on one machine does not tell you what the page serves in the field.

saying these in an interview costs you the question

  • Ignores device pixel ratio and matches sizes to w directly
  • Treats w descriptors as viewport breakpoints
  • Assumes the chosen candidate is guaranteed by spec
  • Expects the browser to downgrade when the window shrinks
  • Builds candidates spaced a few pixels apart

context