skip to content

Image and Font Strategy

Images are usually most of your bytes and often the LCP element; fonts are usually the cause of flashing or shifting text. This group covers the cross-cutting strategy for both and how it feeds back into the vitals.

on this pageshow

explore

questions

28

In CSS, what does the font-display descriptor inside an @font-face rule control, and what is the difference between FOIT and FOUT?

level: juniorimportance: must knowfreq 68%

answer

  1. what to paint while the file downloads
  2. invisible text versus fallback text
  3. one means blank, one means restyle
  4. descriptor lives inside @font-face
  5. swap opts into the visible change

basics

~20 s

font-display tells the browser what to paint while a web font is still downloading: nothing (FOIT, a flash of invisible text) or the fallback family (FOUT, a flash of unstyled text that is later replaced by the web font).

solid answer

~50 s

A font declared with `@font-face` lives in a file that has to be downloaded, so between the moment layout is ready to paint text and the moment the file arrives there is a gap. `font-display` is the author's instruction for what to show during that gap. FOIT — flash of invisible text — means the browser reserves the space but paints no glyphs until the font arrives; the reader sees blank area. FOUT — flash of unstyled text — means the browser paints immediately in the next family from the `font-family` list and repaints in the web font later; content is readable from first paint, but the restyle is visible and usually nudges line breaks. `font-display: swap` opts into FOUT; the default `auto` behaves like `block` in practice, which gives FOIT for the first moments. The descriptor goes inside each `@font-face` block, not on the selector that uses the family.

code

css · 11 lines
css
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: "Inter", system-ui, sans-serif;
}

go deeper

for a junior

Know that FOIT means invisible text and FOUT means fallback text that later changes, and that font-display picks between them inside the @font-face block.

for a middle

Be ready to explain that the descriptor is per face, that the default auto behaves like block, and that swap trades a visible restyle for readable text at first paint.

for a senior

Show you treat the descriptor as damage control, not a fix: say what actually delays the font, and justify the value you picked from how different the fallback looks and how much text uses the face.

for a principal

Own the policy across a design system — one documented default per font role, icon fonts handled differently from body text, and a way to stop new @font-face blocks shipping without the descriptor.

## Why the browser needs a policy at all When a stylesheet declares a family with `@font-face`, the glyph outlines live in a separate file that must be fetched over the network. Layout can be ready to paint a paragraph long before that file lands — on a fast connection the gap is tens of milliseconds, on a congested mobile connection it can be several seconds. The browser has to put *something* on the screen during that gap, and `font-display` is how an author says which compromise they prefer. ## FOIT: flash of invisible text With FOIT the browser lays the text out but paints no glyphs. The box is the right size, the surrounding layout is settled, and the reader sees an empty region where the words should be. Nothing is broken — the text simply has not been drawn yet. Browsers do not do this forever: after a timeout they give up and paint a fallback face so the page is not permanently unreadable. FOIT trades *readability* for *visual stability*: the reader waits, but never sees the text change shape. ## FOUT: flash of unstyled text With FOUT the browser paints straight away using the next family in the `font-family` list, or the generic default if none matches, and then repaints in the web font once it arrives. The content is readable from the first paint. The cost is the visible swap: glyph shapes change mid-read, and because two faces almost never share identical metrics — width per character, x-height, line height — the text can reflow when the swap happens. FOUT trades visual stability for readability. ## Where the descriptor goes `font-display` is a *descriptor* inside `@font-face`, not a property you can set on a selector: ```css @font-face { font-family: "Inter"; src: url("/fonts/inter-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; } body { font-family: "Inter", system-ui, sans-serif; } ``` It applies per face, so a family shipped as four separate weight files needs the descriptor in all four `@font-face` blocks. Writing `font-display: swap` in a normal rule such as `body { … }` does nothing at all — a common review finding. ## The five values at a glance - `auto` — leave it to the browser. In practice mainstream browsers treat this like `block`, so an undeclared `font-display` gives you FOIT. - `block` — hide the text briefly, then swap whenever the font arrives. - `swap` — paint the fallback almost immediately and swap whenever the font arrives. The straightforward FOUT choice. - `fallback` — paint the fallback almost immediately, but only accept the swap within a short window; a very late font is ignored for this page load. - `optional` — paint the fallback almost immediately and never swap on this load; the file is still cached for later navigations. ## What font-display does not do It does not make the font arrive faster and it does not change *when* the request starts — the browser still fetches the file only after style resolution finds an element that actually uses the family. It has no effect on families already installed on the device, because there is nothing to download. And it does not remove the restyle that FOUT causes: `swap` is a decision to *show* the swap rather than hide it. If the real problem is that the font is discovered late, changing the descriptor only changes which bad frame the user sees. ## Which one is usually right For body copy, most teams pick FOUT: unreadable text is a worse failure than restyled text, and a reader who has already started the sentence is not blocked. For an icon font the logic inverts — a fallback family has no matching glyphs, so `swap` renders letters or tofu boxes where icons should be, and briefly showing nothing is the lesser evil. The judgment depends on how different the fallback looks and how much of the page the face covers.

  • If an @font-face rule declares no font-display at all, what happens?
    The value defaults to `auto`, which hands the decision to the browser. In practice mainstream browsers treat `auto` like `block`, so you get a short period of invisible text followed by the font swapping in whenever it arrives. If you want readable text immediately you have to say `swap` explicitly.
  • Does font-display change when the font file is requested?
    No. The request is triggered by style resolution finding an element that uses the family, and `font-display` only governs what is painted while that request is in flight. A late-discovered font is still late; the descriptor just chooses whether the reader stares at blank space or at the fallback during the wait.
  • Why do most teams prefer FOUT over FOIT for body copy?
    Because invisible text is indistinguishable from a broken page — the reader has nothing to do but wait. FOUT lets them start reading at first paint and pays for it with a restyle they can read through. The calculation flips for icon fonts, where the fallback carries no useful glyphs at all.

saying these in an interview costs you the question

  • Thinks font-display controls when the font starts downloading
  • Says font-display: swap removes the visible font change
  • Believes FOIT means the font failed to load
  • Puts font-display in the element rule instead of @font-face
  • Assumes swap is correct for icon fonts too

context

open as a page

When choosing a file format for a page's images, how do you decide between JPEG, PNG and SVG for a photograph, a flat UI illustration, and a logo — and what does each choice cost in bytes?

level: juniorimportance: must knowfreq 66%

basics

~20 s

Match the format to the content: JPEG for photographs, PNG when flat artwork needs exact pixels or transparency, SVG for vector art such as logos and icons that must stay crisp at any size and any screen density.

open as a page

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%

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.

open as a page

In a CSS @font-face rule, how do the font-display values block, swap, fallback and optional differ in terms of the block period and the swap period?

level: middleimportance: must knowfreq 58%

basics

~20 s

Each value sets two windows. block gives a short blocking window then swaps whenever the font lands; swap blocks for almost nothing and swaps at any time; fallback blocks briefly and swaps only within a short window; optional blocks briefly and never swaps on that load.

open as a page

For the same photograph, what do AVIF and WebP actually buy you over JPEG, and what are the tradeoffs that stop you from simply encoding everything as AVIF?

level: middleimportance: must knowfreq 72%

basics

~20 s

Both compress photographs far better than JPEG — WebP typically around a quarter to a third smaller, AVIF usually smaller again — and both support alpha. AVIF costs much more CPU to encode, can smear fine detail, and can inflate tiny flat images.

open as a page

A page's largest visible element is a hero photo applied with a CSS background-image on a <div>. Why does that usually produce a later Largest Contentful Paint (LCP) than the same photo placed in an <img> element, and what would you change?

level: middleimportance: must knowfreq 58%

basics

~20 s

A CSS background image is discovered only after the stylesheet downloads, parses and matches the element, so its request starts much later than an <img> the preload scanner finds in the raw HTML. Put the LCP photo in an <img>.

open as a page

A team serves photos through an image CDN using URLs like /photos/hero.jpg?width=640&format=auto. What does the image CDN actually do with that request, and where does the original file live?

level: juniorimportance: should knowfreq 55%

basics

~20 s

An image CDN keeps one high-resolution master at an origin and derives every variant at request time: it reads the transformation parameters from the URL, resizes and re-encodes the master, returns that derivative, and caches it at the edge for later requests.

open as a page

What does it mean to subset a webfont file, and why does subsetting usually cut its size so dramatically?

level: juniorimportance: should knowfreq 45%

basics

~20 s

Subsetting rebuilds a font file so it contains only the glyphs a site actually renders. Retail fonts ship thousands of glyphs covering scripts, symbols and typographic alternates most pages never use, so dropping them commonly removes more than half the bytes.

open as a page

An image CDN caches the derivatives it generates. What forms the cache key for a derivative, and how does a front-end team accidentally destroy that cache's hit rate?

level: middleimportance: should knowfreq 45%

basics

~20 s

The cache key is the master's path plus the transformation parameters, and sometimes request headers the response varies on. Any change to that key — computed widths, extra tracking parameters, reordered or re-cased parameters — mints a new derivative, forcing an origin fetch and a re-encode.

open as a page

In the browser, how do you load a web font from JavaScript and detect when it is ready to use — for example before drawing text into a canvas?

level: middleimportance: should knowfreq 32%

basics

~20 s

Use the document.fonts font set: call document.fonts.load('1em Brand') to force a face to download and await the returned promise, or construct a FontFace, await its load() and add it to document.fonts. document.fonts.ready resolves when pending font work has settled.

open as a page

In a CSS @font-face rule, what does the unicode-range descriptor do, and how does it change which font files the browser downloads?

level: middleimportance: should knowfreq 45%

basics

~20 s

The unicode-range descriptor declares which codepoints a font face covers. The browser downloads that face only if the page actually renders at least one character in the range, so a family split into per-script faces fetches only the scripts the page uses.

open as a page

A site self-hosts four static weights of one family (400, 500, 600, 700). When does replacing them with a single variable font actually reduce bytes, and when does it make things worse?

level: middleimportance: should knowfreq 40%

basics

~20 s

A variable font stores one set of outlines plus delta data describing how they change along an axis, so it beats three or four separate static weight files but loses to one or two. Using few weights, or shipping the full axis range when you need a slice of it, makes it heavier.

open as a page

A marketing page ships a 6 MB animated GIF above the fold. What should you serve instead, and why is the replacement so much smaller?

level: middleimportance: should knowfreq 44%

basics

~20 s

Serve a muted, looping, inline video, or an animated WebP if it must stay an image element. Video codecs compress across frames and lossily, so the same animation typically costs a tenth of the GIF's bytes or less.

open as a page

Your LCP element is a headline set in a self-hosted web font. How do CSS font-display: block and font-display: swap move Largest Contentful Paint and Cumulative Layout Shift in opposite directions, and how do you decide between them?

level: middleimportance: should knowfreq 48%

basics

~20 s

With CSS font-display: block the headline stays invisible until the font arrives, so LCP waits for the font. With swap it paints immediately in a fallback, giving an early LCP but a reflow when the real font arrives, which lands in CLS.

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

An image CDN serves AVIF, WebP or JPEG from one URL depending on the request's Accept header. What must the response do so that caches in front of it stay correct, and what does that cost?

level: seniorimportance: should knowfreq 36%

basics

~20 s

A response whose encoding depends on the request must tell shared caches so, with Vary: Accept or an equivalent normalized cache key. Without it a cache can hand AVIF bytes to a client that cannot decode them; with a naive Vary it fragments into many near-identical copies.

open as a page

A self-hosted web font declared with font-display: swap only starts downloading about two seconds into the page load, so visitors read the fallback and then watch the text restyle. Why does the font request start so late, and how do you make it start earlier?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Fonts are discovered late because the browser requests one only after downloading and parsing the stylesheet and finding an element that uses the family. A rel=preload link with as="font", the right type and crossorigin starts the fetch alongside the CSS instead.

open as a page

Text visibly reflows when a webfont replaces the fallback. What do the CSS @font-face descriptors size-adjust, ascent-override and descent-override do about that, and what are you matching them against?

level: seniorimportance: should knowfreq 35%

basics

~20 s

They retune a fallback font's metrics to match the webfont's. size-adjust scales glyph outlines and advance widths so text occupies the same width; ascent-override and descent-override replace the metrics that set line-box height. Matched metrics mean the swap changes glyphs without moving anything.

open as a page

A team argues that loading their webfont from a public font service such as Google Fonts is faster than self-hosting the woff2. Which part of that argument stopped being true, and what does self-hosting change about the critical path?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The shared-cache argument is dead: browsers now partition the HTTP cache by top-level site, so a font fetched on another site is refetched on yours. Self-hosting removes a third-party stylesheet-then-font request chain and two extra connection setups from the critical path.

open as a page

A site encodes every image with one global quality setting — say quality 85 — in its image encoder. Why does a single fixed quality number both waste bytes and produce visibly bad images, and how would you choose settings instead?

level: seniorimportance: should knowfreq 46%

basics

~20 s

A quality number is an encoder-specific dial, not a shared scale, and different content degrades at different points. Set per-format defaults, tune per content class, and target a perceptual quality measure or a byte budget rather than one magic number.

open as a page

Two hero images have almost identical file sizes, but one is 4000x3000 pixels and the other is 1200x900. Why can the larger one still hurt Largest Contentful Paint and interaction responsiveness on a mid-range phone?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Transfer is only part of the cost. Decoding, rasterizing and holding an image in memory scale with pixel count, not file size, so a 4000x3000 image costs roughly eleven times the work and memory of a 1200x900 one after the bytes arrive.

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

For a site holding both editorial images committed to the repository and images uploaded by users, how would you decide between generating image derivatives at build time and generating them at request time through an image CDN?

level: principalimportance: should knowfreq 28%

basics

~20 s

Build-time generation fits a known, bounded set of images and yields plain static files with no runtime dependency; request-time generation is required for content that arrives after deployment. Most real sites end up hybrid, split on whether the image set is enumerable at build.

open as a page

How would you decide which font-display value to ship for a product's primary text font, and what is the case for choosing optional?

level: principalimportance: should knowfreq 28%

basics

~20 s

Decide from the role of the face and how visibly different the fallback is: swap for body copy, where readable text beats a restyle; block-like behaviour only for icon or symbol faces; optional when a mid-read restyle is worse for the product than some first-time visitors never seeing the brand font.

open as a page

You own a large site with thousands of existing JPEG and PNG assets. How do you decide whether converting them to modern formats is worth doing, and how do you keep that decision from becoming a permanent maintenance tax?

level: principalimportance: should knowfreq 38%

basics

~20 s

Decide with data: find how much of real page weight images actually are, rank candidates by traffic times bytes, and convert only where the saving beats the encode, storage and fallback cost. Then apply the policy automatically rather than asset by asset.

open as a page

A marketing team wants an autoplaying full-bleed background video as the hero on the site's highest-traffic landing page. What would you require before shipping it, framed in terms of Largest Contentful Paint and Cumulative Layout Shift?

level: principalimportance: should knowfreq 30%

basics

~20 s

Treat the poster image as the real hero: it is the LCP candidate and gets full image discipline. Reserve the video box with a fixed aspect ratio so nothing shifts, defer the video bytes until after the poster paints, and agree a field-data kill criterion up front.

open as a page

An image CDN accepts arbitrary width, height and quality parameters on a public transformation endpoint. How can that endpoint be abused, and what controls does a team put in front of it?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

An open transformation endpoint lets anyone mint unlimited novel derivatives, each forcing an origin fetch and a CPU-heavy encode that you pay for. Teams close it with signed URLs, an allowlist of permitted transformations or named presets, dimension caps, and edge rate limiting.

open as a page