Responsive images: picture, srcset and sizes
Responsive images let the browser pick the right image file for each device automatically, based on viewport width, pixel density, and format support.
Theory
TL;DR
srcset+sizes: list available files with their widths, tell the browser how wide the image displays, and the browser does the rest.picture: you write explicit media queries, the browser follows them exactly. You control which image loads per breakpoint.- Same image, different sizes →
srcset. Different crops or formats per device →picture. - A 375px phone with a 2x display needs a 750px image, not a 375px one. The browser calculates this from
sizes. - Analogy:
srcsetis a menu with portion sizes where the server picks based on your needs.pictureis when you point at exactly what you want.
Quick example
<!-- Browser picks the best image based on viewport + pixel density -->
<img
src="photo-small.jpg"
srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Product photo"
>
<!-- 375px phone (1x): loads photo-small.jpg (100vw = 375px, 400w covers it) -->
<!-- 375px phone (2x): loads photo-medium.jpg (375 x 2 = 750px needed, 800w fits) -->
<!-- 1920px desktop: loads photo-large.jpg (33vw = 640px, 1600w covers 2x) -->The src attribute is the fallback for old browsers that ignore srcset entirely.
Key difference
srcset with width descriptors tells the browser which files exist and how wide each one is. sizes tells it how wide the image actually renders on screen. The browser combines both to pick the right download. picture works differently: it evaluates <source> elements against explicit media queries and loads the first match, with srcset inside each source handling resolution variants. Use srcset when the image scales. Use picture when the image itself changes.
When to use
srcset+sizes: product photos, blog images, hero images that scale proportionally across devicespicture: hero images that need different crops on mobile vs desktop, format switching (WebP with JPEG fallback), images where the subject changes per breakpoint- Both together:
picturehandles which crop to show,srcsetinside each<source>handles which resolution of that crop to load - Single
src: SVGs, icons, small decorative images that don't vary by device
Comparison table
| Aspect | srcset + sizes | picture element | Single src |
|---|---|---|---|
| Use case | Same image, different sizes | Different images per breakpoint | Static, device-agnostic |
| Who decides? | Browser (viewport + DPR) | You (via media queries) | No choice |
| Bandwidth savings | High | High | None |
| Art direction | No | Yes | No |
| Format switching | No | Yes | No |
| Best for | Scaling photos, galleries | Different crops, WebP/JPEG fallback | Icons, SVG |
How the browser selects an image
When the browser hits srcset + sizes, it reads the sizes attribute to get the display width in CSS pixels, multiplies by the device pixel ratio, then picks the smallest image in srcset that covers that pixel count without blurring. For picture, it goes through <source> elements top to bottom, stops at the first matching media and type, then runs the same algorithm on any srcset inside that source. The browser caches this decision and won't re-request unless the viewport changes significantly.
One thing that surprises people: the browser can factor in cached images and network conditions. If a larger file is already in cache, it may serve that instead of fetching the theoretically better match.
Common mistakes
Mistake 1: Leaving out sizes
<!-- Wrong: browser defaults to 100vw and may load an oversized file -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
alt="Photo"
/>
<!-- Right: tell the browser the actual display width -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Photo"
/>Without sizes, a thumbnail on a 1440px desktop triggers a download of the full-width image. The bandwidth cost is real.
Mistake 2: Mixing width and pixel density descriptors
<!-- Wrong: browser ignores the entire srcset -->
<img
src="logo.png"
srcset="logo.png 1x, logo-2x.png 2x, logo-wide.png 800w"
alt="Logo"
/>
<!-- Right: use one descriptor type, not both -->
<img
src="logo.png"
srcset="logo.png 1x, logo-2x.png 2x"
alt="Logo"
/>Width descriptors need sizes to work. Pixel density descriptors are absolute. Mixing them breaks the selection algorithm and the browser falls back to src.
Mistake 3: sizes doesn't match your CSS
<!-- Wrong: CSS caps the image at 600px, but sizes says 100vw -->
<style>img { width: 100%; max-width: 600px; }</style>
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-1200.jpg 1200w"
sizes="100vw"
alt="Photo"
/>
<!-- On a 1440px desktop: browser downloads photo-1200.jpg -->
<!-- but CSS renders it at 600px. Bandwidth wasted. -->
<!-- Right: sizes reflects the CSS constraint -->
<img
src="photo.jpg"
srcset="photo-400.jpg 400w, photo-1200.jpg 1200w"
sizes="(max-width: 640px) 100vw, 600px"
alt="Photo"
/>Mistake 4: Using picture for simple scaling
<!-- Overcomplicated for what srcset handles better -->
<picture>
<source media="(max-width: 640px)" srcset="photo-small.jpg" />
<source media="(max-width: 1024px)" srcset="photo-medium.jpg" />
<img src="photo-large.jpg" alt="Photo" />
</picture>
<!-- Simpler, and adapts to future screen sizes automatically -->
<img
src="photo-small.jpg"
srcset="photo-small.jpg 400w, photo-medium.jpg 800w, photo-large.jpg 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Photo"
/>Hardcoded media queries in picture break on devices outside your expected breakpoints. srcset lets the browser adapt to any screen automatically.
Mistake 5: Expecting the smallest image on a small phone
A 375px phone with a 2x display loads the 800w image, not the 400w one. The math: 375 x 2 = 750px needed, so the browser picks 800w as the smallest option that won't degrade quality. This is correct behavior. Account for DPR when deciding which image sizes to generate.
Real-world usage
- Next.js
<Image>component: generatessrcsetautomatically, you only setsizesand dimensions - Shopify product images:
picture+srcsetfor mobile crop and WebP switching - WordPress with Smush and similar plugins: automatically generates
srcsetfor all uploaded images - Gatsby
gatsby-plugin-image: generatessrcsetwith blurred placeholders - Cloudinary and Imgix: CDN services that generate
srcsetURLs on demand, no manual file creation needed - CSS backgrounds:
image-set()for the same idea in CSS (background-image: image-set(url(photo.jpg) 1x, url(photo-2x.jpg) 2x))
Follow-up questions
Q: Why can't you mix width descriptors (400w) and pixel density descriptors (2x) in the same srcset?
A: They use different selection algorithms. Width descriptors need sizes to calculate display width first. Pixel density descriptors are absolute and skip that step. Mixing them creates ambiguity the browser won't resolve, so it drops the entire srcset and loads src.
Q: If I use picture with media queries, do I still need srcset inside <source> elements?
A: Yes. picture handles art direction (which version of the image). srcset inside each <source> handles resolution variants of that version. On a 2x phone, picture picks the mobile source, then srcset picks the 2x variant of that mobile image.
Q: A phone is loading a larger image than expected. What do you check first?
A: Device pixel ratio. Most modern phones are 2x or 3x, so the browser requests 750 to 1125px worth of image. Open Chrome DevTools, go to Network, filter by Img, and check which srcset option actually loaded.
Q: What happens if no <source> in <picture> matches?
A: The browser falls through to the <img> inside <picture>, which acts as the required fallback. That is why <img> is mandatory inside <picture>, not optional.
Q: (Senior level) A designer gives you a 3000x2000px image for a responsive page. Walk through your decision process.
A: First question: does the image content change per device, or just scale? Same subject means srcset + sizes. Mobile needs a portrait crop means picture for art direction. Then calculate actual display widths: mobile 100vw (~375px), tablet 50vw (~512px), desktop 33vw (~640px). Generate at 1x and 2x: six files total. Automate with a CDN or build tool rather than manual exports. Verify in DevTools that each device loads the right file.
Examples
Basic srcset for a product grid
<!-- Image fills full width on mobile, half on tablet, a third on desktop -->
<img
src="product-800.jpg"
srcset="
product-400.jpg 400w,
product-800.jpg 800w,
product-1600.jpg 1600w
"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Blue running shoes"
>
<!-- 375px phone (1x) → product-400.jpg (375px needed) -->
<!-- 375px phone (2x) → product-800.jpg (750px needed) -->
<!-- 1440px desktop (1x) → product-800.jpg (1440 x 0.33 = ~475px) -->
<!-- 1440px desktop (2x) → product-1600.jpg (~950px needed) -->The sizes values must match your CSS layout. If the grid changes from three columns to four, update sizes to match.
picture for art direction with WebP fallback
<!-- Hero: portrait crop on mobile, landscape on desktop, WebP where supported -->
<picture>
<!-- Mobile portrait crop, WebP -->
<source
media="(max-width: 640px)"
type="image/webp"
srcset="hero-mobile.webp 375w, hero-mobile@2x.webp 750w"
/>
<!-- Mobile portrait crop, JPEG fallback -->
<source
media="(max-width: 640px)"
srcset="hero-mobile.jpg 375w, hero-mobile@2x.jpg 750w"
/>
<!-- Desktop landscape, WebP -->
<source
type="image/webp"
srcset="hero-desktop.webp 1200w, hero-desktop@2x.webp 2400w"
/>
<!-- Fallback for browsers that don't support picture -->
<img src="hero-desktop.jpg" alt="Team working in the office" />
</picture>
<!-- Chrome on mobile → hero-mobile.webp (or @2x variant) -->
<!-- Safari on mobile → hero-mobile.jpg -->
<!-- IE11 → hero-desktop.jpg (ignores picture) -->The browser reads <source> elements in order. The first one where both media and type match wins.
Format switching without changing the crop
<!-- Same image, just serve WebP to browsers that support it -->
<picture>
<source
type="image/webp"
srcset="photo.webp 400w, photo-2x.webp 800w, photo-3x.webp 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
/>
<img
src="photo.jpg"
srcset="photo.jpg 400w, photo-2x.jpg 800w, photo-3x.jpg 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
alt="Landscape photo"
/>
</picture>
<!-- Chrome, Firefox, modern Edge → WebP (typically 25-35% smaller than JPEG) -->
<!-- Safari 13 and older → JPEG via img srcset -->
<!-- IE11 → photo.jpg from img src only -->This is the standard pattern for serving modern formats with a safe fallback. No server-side logic needed.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.