QR Codes 101: How They Actually Work
3 min read
Most explainers tell you a QR code “stores a link.” That is the symptom, not the mechanism, and it hides every decision that actually determines whether your code scans reliably in the field. If you are evaluating QR for a product, a print run, or a feature, the useful questions are not “what is a QR code” but “which version, which error-correction level, static or dynamic, and what does each choice cost me.” This is the engineer’s version of the tour.
What the black squares actually encode
A QR code is a 2D matrix of modules (the small squares) on a grid. The three large squares in the corners are finder patterns; they let a scanner locate and orient the code regardless of rotation. Smaller alignment patterns keep things straight on larger or slightly warped codes, and the dotted timing patterns between finders act as a coordinate ruler.
The data itself is encoded in one of several modes: numeric, alphanumeric, byte, or kanji. Mode matters because it is a density trade-off. Numeric packs roughly 3.3 bits per digit; byte mode spends a full 8 bits per character. A code holding only digits fits far more than the same physical size holding a mixed URL. If you control the payload, choosing the tightest mode that fits your data is free capacity.
Error correction: the trade-off nobody reads about
Every QR code carries Reed-Solomon error-correction data, and you pick how much: levels L, M, Q, or H, recovering roughly 7%, 15%, 25%, and 30% of the symbol respectively. This is the single most underrated lever.
Higher correction means a code survives smudges, creases, glare, or a logo dropped in the middle. But that redundancy is not free. It consumes capacity, so at a fixed data size a higher level forces a larger version with more modules, which means smaller modules at the same print size and a harder scan from distance. The honest framing: L and M are fine for clean screens and controlled print; Q and H earn their cost on packaging, stickers, and anything that gets handled or partly obscured. Defaulting everything to H “to be safe” is a common mistake that quietly shrinks your modules.
Version and size: scaling has limits
QR “version” refers to size, from version 1 (21x21 modules) up to version 40 (177x177). More data pushes you to a higher version. The trade-off is physical: more modules at the same printed dimensions means each module is tinier, and scanners need a minimum module size relative to camera distance. A dense version 20 code on a business card may simply fail to resolve.
The practical move is to keep the payload short. A long tracking URL inflates the version; a shortened URL keeps it low and scannable. This is also the strongest argument for thinking about static versus dynamic.
Static vs dynamic: where the real decision lives
A static QR encodes your destination directly in the modules. It is permanent, free, works offline forever, and exposes nothing to a third party. The catch: change the destination and you must reprint, and you get zero analytics.
A dynamic QR encodes a short redirect URL pointing at a server you control, which forwards to the real target. You can edit the destination after printing, run A/B tests, and collect scan analytics. The cost is a permanent dependency: if that redirect service disappears or the domain lapses, every printed code dies. You are also routing your users through someone else’s infrastructure.
My rule of thumb: static for anything that should outlive a vendor (Wi-Fi credentials, a vCard, a stable URL), dynamic only when you genuinely need post-print editing or measurement, and only behind a domain you own. The technology is the same matrix either way. The real trade-off is who controls the link after it is printed, and that is a decision worth making on purpose rather than by default.