skip to content

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%

answer

  1. quality scales are encoder-specific
  2. the curve flattens near the top
  3. gradients and text fail first
  4. target a perceptual score per image

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.

solid answer

~50 s

Quality scales are internal to each encoder: JPEG at 85, WebP at 85 and AVIF at 85 are three unrelated settings, and the AVIF number that matches a JPEG 85 is usually much lower. They are also non-linear — above roughly 80 on JPEG you are mostly paying for detail nobody sees, which is where the wasted bytes come from. Meanwhile the same setting that flatters a noisy photograph will band a smooth gradient, ring around UI text, and smear fine texture, so one number is simultaneously too generous and too aggressive. In practice I set a per-format baseline, override it by content class — photographic, flat art, screenshots with text — and where volume justifies it, drive encoding against a perceptual metric such as SSIMULACRA2 or butteraugli so each image gets the lowest bitrate that still hits a quality target. Then I spot-check the worst outliers by eye.

code

javascript · 15 lines
javascript
const sharp = require('sharp');

const PRESETS = {
  photo: { quality: 55, effort: 4 },
  screenshot: { quality: 70, effort: 4, chromaSubsampling: '4:4:4' },
};

async function encode(file, contentClass) {
  return sharp(file).avif(PRESETS[contentClass]).toBuffer();
}

(async () => {
  const shot = await encode('ui-capture.png', 'screenshot');
  console.log('screenshot bytes:', shot.length);
})();

go deeper

for a junior

Know that lossy encoders have a quality setting, that lower means smaller and worse, and that the same number does not mean the same thing in two different formats.

for a middle

Explain why the curve is non-linear and why content matters: gradients band, text rings, and noisy photographs hide artefacts, so one setting cannot suit all three.

for a senior

Show a working method — per-format baselines, content-class presets, encoding against a perceptual target, a byte cap for outliers, and re-encoding from originals rather than derivatives.

for a principal

Own the tradeoff between image fidelity and page weight as a product decision: who sets the quality bar, how it is evidenced, and how regressions surface before customers see them.

## A quality number is a dial, not a unit The most common misunderstanding about lossy encoding is that "quality 85" means something. It does not, on its own. Each encoder maps its 0–100 input onto its own internal quantisation decisions, tuned to its own codec. Consequences: - **It does not transfer across formats.** A JPEG at 85 and an AVIF at 85 are not comparable, and AVIF's usable range sits far lower — settings in the 50s often match a JPEG in the 80s. - **It does not always transfer across encoders of the same format.** Two JPEG encoders at the same nominal quality can produce different sizes and different artefacts. - **It is not linear.** JPEG's curve flattens hard: going from 80 to 95 can nearly double the file for a difference most people cannot see side by side, let alone on a phone in daylight. So one global number is a guess that happens to be right for some images and wrong for the rest — wasting bytes where the content could take more compression, and shipping visible damage where it could not. ## Content decides how much loss you can hide Lossy codecs are betting on human perception, and the bet's odds depend entirely on the picture: - **Noisy, detailed photographs** hide artefacts beautifully. Grain and busy texture mask quantisation error, so these tolerate aggressive settings. - **Smooth gradients** — skies, soft studio backgrounds, blurred bokeh — are the opposite. With too few levels available the gradient collapses into visible bands, and once you have seen banding you cannot unsee it. - **Text and UI screenshots** have hard high-contrast edges, exactly what frequency-domain quantisation handles worst; you get ringing halos around glyphs. Chroma subsampling makes coloured text worse still. - **Flat vector-like artwork** should usually not be lossy at all; lossless modes are often smaller *and* exact. - **Fine repeated texture** — fabric, foliage, hair — is where AVIF in particular smooths detail into a waxy plane rather than showing blocks. It fails politely, which makes it easy to ship. ## What to do instead of one number **1. Per-format baselines.** Keep separate defaults per format, established by comparing outputs on your own images, not by copying a blog post. **2. Per-content-class overrides.** Classify assets by the job they do — hero photography, product shots on white, avatars, screenshots, diagrams — and give each class its own settings. Most sites need only three or four classes. ```javascript const PRESETS = { photo: { avif: { quality: 55, effort: 4 }, webp: { quality: 78 } }, productShot:{ avif: { quality: 62, effort: 4 }, webp: { quality: 82 } }, screenshot: { avif: { quality: 70, effort: 4, chromaSubsampling: '4:4:4' }, webp: { quality: 90 } }, }; ``` The screenshot preset is the interesting one: higher quality *and* full-resolution chroma, because text edges are the failure mode. **3. Target a quality, not a setting.** At volume, the better approach is to search for the smallest file that still reaches a perceptual quality target, per image. Perceptual metrics such as SSIMULACRA2 and butteraugli (both from the libjxl project) model human vision far better than PSNR or plain SSIM, and encoding against a target score gives you a consistent *look* across a heterogeneous library rather than a consistent number. **4. Cap by byte budget as a second constraint.** "Hit this quality target, but never exceed N kilobytes at this width" catches the pathological image that would otherwise ship at two megabytes because it is genuinely hard to compress. **5. Turn off chroma subsampling where colour edges matter.** 4:2:0 halves colour resolution and is the right default for photographs. For screenshots, logos and saturated graphics, 4:4:4 costs bytes and saves the image. **6. Keep the source of truth.** Always re-encode from the original, never from a previously compressed derivative — generational loss compounds, and it is irreversible. ## Verifying, and keeping it verified Eyeballing one image on a good monitor proves nothing. Sample across content classes, view at the size and density users actually get, and pay attention to the specific failure modes above rather than a general impression. Once encoding is automated, record per-asset output size and perceptual score so a change to encoder settings or version shows up as a diff, and flag outliers — the biggest files and the lowest scores — for human review. That is what stops a well-meaning "let's drop quality by ten" from quietly damaging the gradient-heavy half of the library.

  • Which kinds of image suffer most from an aggressive lossy setting, and why?
    Smooth gradients band, because too few levels survive quantisation. Text and UI screenshots ring, because hard high-contrast edges are what frequency-domain coding handles worst. Fine repeated texture smears, especially in AVIF, which smooths rather than blocks. Noisy busy photographs are the opposite case — their own detail masks the artefacts, so they tolerate far more compression.
  • What does turning off chroma subsampling actually buy you, and what does it cost?
    Full-resolution colour, so coloured text, logos and saturated edges stop fringing or bleeding. It costs bytes — you are storing two colour planes at full size instead of quarter size — which is why 4:2:0 remains the right default for photographs. Reserve 4:4:4 for content whose failure mode is coloured edges.
  • How do you stop quality regressions from shipping once encoding is automated?
    Always re-encode from the original source, record each asset's output size and perceptual score alongside it, and treat a change in either as a reviewable diff when encoder settings or versions change. Flag the outliers — largest files, lowest scores — for a human to look at. Automation without recorded evidence just makes bad encodes arrive faster.

saying these in an interview costs you the question

  • Quality 85 is the industry standard, use it everywhere
  • AVIF quality 85 matches JPEG quality 85
  • Higher quality numbers always look meaningfully better
  • It looked fine on my monitor, so it is fine
  • Just compress harder until the page hits its budget

context