skip to content

Responsive Image Strategy

Serving a 2000px hero to a phone wastes most of your image budget. The strategy question is which widths you generate and how honestly you describe the layout size to the browser.

on this pageshow

questions

4

A page ships one 2000px-wide hero photo and lets CSS scale it down to fit whatever screen it lands on. Why is that a performance problem on a phone, and what does serving a set of widths instead actually save?

level: juniorimportance: must knowfreq 70%

answer

  1. CSS resizes after the download
  2. bytes follow pixel area, not width
  3. roughly six times the pixels on a phone
  4. compare fetched width to layout width times density

basics

~20 s

The browser downloads a 2000px image's full bytes no matter how small CSS draws it, so a phone that needs roughly 800 pixels of width pays for several times the pixels it can show. Serving a ladder of widths recovers that budget.

solid answer

~50 s

CSS sizing happens after the download, so `width: 100%` on a 2000px file does not make the file smaller — the phone still pays for every byte and then throws most of the pixels away. Bytes track pixel area, so a 2000x1200 source is about six times the pixel count of the 800x480 a 400 CSS-px column at device pixel ratio 2 actually needs; in practice that is often 350 KB versus 70 KB for the same photo. Those wasted bytes compete for bandwidth with the resources that block first render, and on a hero image they sit directly in front of the page's largest paint. The fix is to generate a small ladder of widths of the same image and let the device download the one closest to its layout size times its pixel density, which typically cuts image weight on mobile by more than half.

code

javascript · 17 lines
javascript
// Rank the images on this page by how oversized the fetched file is.
const dpr = window.devicePixelRatio;
const report = [...document.images]
  .map((img) => {
    const displayed = img.getBoundingClientRect().width;
    const needed = displayed * dpr;
    return {
      src: img.currentSrc || img.src,
      fetchedWidth: img.naturalWidth,
      neededWidth: Math.round(needed),
      oversizeFactor: needed > 0 ? +(img.naturalWidth / needed).toFixed(2) : 0,
    };
  })
  .filter((r) => r.oversizeFactor > 1.2)
  .sort((a, b) => b.oversizeFactor - a.oversizeFactor);

console.table(report);

go deeper

for a junior

Be ready to say plainly that the browser downloads the whole file before CSS scales it, and that the phone therefore pays for pixels it never shows. Naming that one fact clearly is most of the answer.

for a middle

Explain why bytes scale with pixel area rather than width, and put rough numbers on it — a 2000px hero versus the 800px a phone needs is several hundred kilobytes. Mention decoded memory as a second cost.

for a senior

Show how you would find the offenders rather than assert them: compare the fetched intrinsic width against layout width times pixel density, rank the page by that ratio, and fix the top of the list first.

for a principal

Own the boundary: which asset classes earn a variant pipeline at all, and what the ongoing cost of that pipeline is against the bytes it saves. Be able to say where the savings stop paying for the machinery.

## The download happens before CSS gets a say The most common misunderstanding here is that a stylesheet can make an image cheaper. It cannot. The browser fetches the resource named in the markup, in full, and only afterwards paints it at whatever size layout has decided on. `width: 100%`, `max-width: 100%`, and `object-fit: cover` are all painting instructions. A 2000px-wide JPEG that renders inside a 360px phone column cost exactly the same number of bytes as the same file rendered full-bleed on a 27-inch monitor. "It looks fine on my phone" is a statement about rendering, never about cost. ## The arithmetic of wasted pixels File size for a photographic image tracks pixel *area*, not width, so oversizing compounds. A 2000x1200 image is 2.4 million pixels. A phone showing that image in a 400 CSS-px-wide column on a screen with a device pixel ratio of 2 needs about 800x480 — 384,000 pixels, roughly one sixth as many. Lossy compression softens the relationship a little (halving the width usually cuts bytes by somewhat less than four times, because per-pixel entropy rises as you shrink), but the direction is unambiguous. In real numbers a well-compressed 2000px hero often lands around 300–400 KB while its 800px sibling lands around 60–90 KB. That single image is wasting a quarter megabyte. Multiply by a product grid with twenty thumbnails and the waste is no longer a rounding error — it is usually the largest single line item in the page's transfer weight, ahead of JavaScript on most content sites. ## Why the phone pays twice The network cost is the obvious one, and it is worse than desktop measurement suggests: mobile links have higher latency and far more variance, and a user on a metered plan pays cash for the difference. But there is a second cost that survives even a fast connection. Image bytes are not free bandwidth — they contend with the stylesheets, fonts, and scripts that gate first render, and a browser that is streaming 400 KB of hero image is not streaming something else. If the oversized image is the page's biggest visible element, its download time is the page's largest-paint time; nothing about that improves because the connection is fast in the office. A third cost is memory. A decoded image lives in memory uncompressed, at roughly four bytes per pixel: a 2000x1200 bitmap is about 9.6 MB resident, versus about 1.5 MB for the 800x480 version. On a low-end phone with a gallery of these, that is a real constraint, not a theoretical one. ## What a width ladder changes The responsive strategy is to generate the same image at several widths — a small ladder such as 400, 640, 960, 1440, 2000 — and let each device download the one closest to what it will actually display, given its layout width and its pixel density. Nothing about the visual result changes; the phone still shows a sharp image. What changes is that it downloads 70 KB instead of 350 KB. That is the entire point: responsive images are a *byte* optimization, not a *quality* one, and if a change makes the image look worse you have done something other than resolution switching. ## Finding the offenders You do not have to guess which images are oversized. For any rendered image, compare the intrinsic width of the file that was actually fetched against the width it is laid out at, multiplied by the screen's pixel density. A ratio near 1 is correct; a ratio of 2 means four times the necessary pixel data; a ratio of 5 on a hero is the classic "one big image for everyone" signature. Running that check across a page produces a ranked list of exactly which assets to fix first, and it is far more useful than a total-bytes number because it tells you *how much* smaller each file should be. ## When one width is still the right answer The tradeoff is not free. Every additional variant is another asset to generate, store, and cache, and the savings are proportional to the image's size. For a 4 KB avatar or a small icon, a ladder buys nothing measurable and costs pipeline complexity forever. The rule of thumb most teams settle on: images that can plausibly exceed a few tens of kilobytes at their largest layout size earn a ladder; everything below that ships once. Spend the machinery where the bytes are.

  • The hero image is 300 KB and the page also ships 600 KB of JavaScript. Why fix the image first?
    Because it is usually cheaper and more certain. Regenerating an image at the right width is a pipeline change with no behavioural risk and often lands a 200 KB saving the same day; splitting 600 KB of JavaScript means touching product code and reasoning about what breaks. It is also the visible element, so the saving shows up directly in the page's largest paint rather than in a metric users do not see.
  • Does this reasoning apply to SVG and to icons the same way?
    No. An SVG is resolution-independent — one file scales to any size, so there is nothing to ladder, and its cost tracks path complexity rather than display size. Small raster assets are similar in practice: below roughly ten kilobytes, the difference between the ideal width and a single shared width is not measurable, and a ladder just adds build and cache complexity for no user-visible gain.
  • If bandwidth keeps getting cheaper, does this stop mattering?
    It has not so far. Median connections improved for a decade while median page weight grew faster, and the users who suffer most are on the slow tail, not the median. Wasted image bytes also cost decoded memory on low-end devices, which does not improve with the network at all. The economics of shipping six times the pixels a screen can show do not change with link speed.

Shipping every customer the largest size in stock and telling them to take it in themselves — the tailoring is free, the freight is not.

saying these in an interview costs you the question

  • Thinks CSS width shrinks the downloaded file
  • Assumes fast connections make oversized images harmless
  • Believes the browser downloads only the visible part of an image
  • Treats image weight as too small to matter next to JavaScript
  • Adds variants to tiny icons where savings are unmeasurable

context

open as a page

A design team asks for 3x image assets so photos look sharp on high-density phone screens. How would you decide how far up the density ladder to go, and what does compression quality have to do with the answer?

level: middleimportance: should knowfreq 38%

basics

~20 s

Going from 2x to 3x costs about 2.25 times the pixels for a perceptual gain most people cannot see, so most teams cap at 2x. The lever that matters more is compressing high-density variants harder, since their artifacts fall below one CSS pixel.

open as a page

You are setting up a pipeline that generates several widths of every photo on a site. How do you decide which widths to generate, and where does the ladder stop at the top?

level: middleimportance: should knowfreq 50%

basics

~20 s

Derive widths from the image's own maximum layout width at each breakpoint times the highest pixel density you commit to, not from viewport breakpoints. Space steps by a roughly constant percentage so savings are even, and cap the ladder where the largest layout can use it.

open as a page

A team proposes generating an image variant every 50 px of width so that every device downloads an almost pixel-perfect fit. What does that granularity cost, and how many widths would you actually ship?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Fine granularity buys a few kilobytes per request while splitting traffic across dozens of URLs, so each variant is requested less, cache hit rates fall and cold misses land on real users. Five or six widths per image role captures nearly all the saving.

open as a page