How QR Codes Work: Error Correction, Versions, and Designing Codes That Scan
What the patterns in a QR code actually encode, how Reed–Solomon error correction lets codes survive logos and damage, and the practical rules for size, contrast and print.
Reading the anatomy
The three large squares are finder patterns — they let a scanner locate and orient the code from any rotation. The smaller inner squares are alignment patterns for perspective correction; the alternating line between finders is the timing pattern that establishes the module grid. Everything else is data and error-correction codewords, laid out in a zigzag with a mask applied to avoid confusing runs of same-color modules.
Capacity scales with version: version 1 is 21×21 modules, version 40 is 177×177 holding up to ~4,296 alphanumeric characters. More data means a denser grid — and a harder scan from a distance.
Error correction is a design budget
QR codes carry Reed–Solomon error correction at four levels: L recovers ~7% damage, M ~15%, Q ~25%, H ~30%. This redundancy is why a code still scans with a logo punched through its middle — the logo *is* damage, spent from the correction budget. Center logos should cover well under the level’s ceiling (under ~10% of area at level H is comfortable), because real-world scuffs, glare and print defects draw from the same budget.
Rules for codes that actually scan
- Quiet zone: keep a margin of 4 modules of background around the code; crowding it is the most common scan failure.
- Contrast: dark modules on light background, roughly 4.5:1 or better. Inverted (light-on-dark) codes fail in many scanner apps.
- Print size: minimum ~2×2 cm for arm’s-length scans; for posters, scan distance ÷ 10 is a serviceable size rule.
- Shorten the payload: a shorter URL means a lower version, bigger modules, and scans from farther away.
- Test on paper, not just on screen — ink spread and matte lamination change everything.
What to encode: payload formats that phones understand
A QR code is just text; what the phone *does* with it depends on recognizable formats. A URL (https://…) opens the browser — always include the scheme, or some scanners treat it as plain text. WIFI:T:WPA;S:NetworkName;P:password;; joins a network without typing. mailto:, tel: and sms: trigger the obvious apps. A vCard (BEGIN:VCARD … END:VCARD) offers to save a contact; a calendar event uses BEGIN:VEVENT. Payment standards (EPC for SEPA transfers, various national schemes) have their own layouts. For anything custom, encode a short URL that redirects to the real destination — it keeps the code small, lets you change the target, and gives you scan analytics.
- Shorter payload → lower version → bigger modules → scans from farther away; keep URLs under ~50 characters where you can.
- Use uppercase for alphanumeric mode when the payload allows (e.g. serials); it packs tighter than mixed case in byte mode.
- Test the exact payload on both iOS and Android camera apps — WiFi and vCard handling differs between them.
Branding a code without breaking it
Colored and logo-bearing codes scan fine when they respect three limits. Keep the three finder patterns intact and high-contrast — scanners lock onto those first, and stylizing them is the fastest way to kill a code. Keep foreground darker than background; gradient fills are fine if every module stays clearly darker than the quiet zone. And budget the logo against error correction: at level H you can hide roughly 25% of the *data area*, but the code must survive print defects too, so aim for a logo covering no more than 10–15% of the total code area, centered, with a small white margin around it. Rounded modules and dot styles are cosmetic and generally safe; "liquid" connected styles that merge modules are the ones that fail on older scanners.
Static vs dynamic codes
A static code encodes the destination directly: free, permanent, private, but unchangeable once printed. "Dynamic" codes encode a redirect URL whose target you can edit later — at the cost of depending on the redirect service’s uptime, subscription and analytics tracking. For anything long-lived in print, either accept the static trade-off or run the redirect on a domain you control.