STI · your phone color the simtable Challenge ↗

Summer Teacher Institute · the code in your hand

How a QR code works — and why you can rip it and it still reads

Type into the box. The code below is encoded from scratch, right here in this page — no library, no network. Then take it apart, and then break it on purpose.

A few minutes ago you pointed your phone at a square of noise and it dropped you into a shared wildfire model. That square is version 4, error-correction level M, 33×33 modules, and it holds 53 bytes: https://apps.simtable.com/AnyHazard/?a=supercomputing. It is the seed string below. Every mark in it is doing a job — and by the end of this page you'll be able to tear a hole in it and watch your phone read it anyway.

Type any text. The QR code re-encodes live.

scan me
bytes in
53
version
4
modules
33²
codewords
100
data cw
64
EC cw
36

↑ the ten versions this page builds, and the byte capacity of each at the current level. Struck-through = too small for your string.

encoder: byte mode (0100) · versions 1–10 · L/M/Q/H · real Reed–Solomon over GF(256) · all 8 masks scored. ~450 lines, no dependencies. View source.

1 · the string

Bytes go in. A version comes out.

A QR code is not a picture of your text — it's your text, packed into a grid of a legal size. There are 40 legal sizes, called versions. This page builds the first ten.

The size rule is arithmetic you can do in your head:

modules = 21 + 4 × (version − 1)

Version 1 is 21×21. Version 2 is 25×25. Version 4 — the one you scanned — is 33×33. Every step up adds a ring of 4 modules and a lot more room.

The encoder picks the smallest version your bytes fit in. So type into the box and hold down a key: you'll watch the code sit still, sit still, sit still — and then jump. That jump is a version boundary. There's nothing gradual about it; you either fit or you don't.

Where the room goes

Your 53 bytes cost 424 bits, plus 12 bits of header (4 bits saying "this is byte mode", 8 bits saying "how many bytes"). That's 436 bits = 55 codewords of the 64 the current version and level give you. The leftovers get stuffed with the pad bytes 0xEC 0x11 0xEC 0x11… — a real, specified pattern, not zeros, chosen because it doesn't look like anything else in the code.

2 · the anatomy

Every mark has a job.

Switch the code to Anatomy (the tab under it) and the noise resolves into parts. Hover or click a part below to isolate it.

Pick a region

Click any part above and it lights up on the code, alone.

Why three corners and not four?

The three big bullseyes are finder patterns, and they're the reason a QR code reads at any angle, upside down, from across a room. Their 1:1:3:1:1 ratio of dark-to-light survives being scanned along any line through the center — so a phone can sweep for them without knowing the orientation first.

Three points fix position, rotation, and scale all at once. Add a fourth and you learn nothing new — but with only three, the asymmetry tells the phone which way is up. Four identical corners would be ambiguous under 90° rotation. So the fourth corner is left free, and that's where the alignment pattern lives: a smaller bullseye that corrects for the code being bent, curved, or photographed at a slant.

The timing patterns — the dotted lines running between the finders — are the ruler. They alternate dark-light-dark-light so the decoder can count: this is how wide one module is, here. Without them, a code on a coffee cup would drift.

3 · reed–solomon

Redundancy you can see.

Switch the code to Data + EC. The blue modules are your string. The turquoise modules are error-correction codewords — arithmetic computed from your string, carried alongside it, so the message can be rebuilt if part of it is destroyed.

They're not a copy. A copy would be twice the size and twice as fragile. They're the remainder of a polynomial division, and that's what makes them powerful.

The actual math, in four sentences

Treat your 64 data codewords as the coefficients of a polynomial over GF(256) — a finite field with exactly 256 elements, where every byte is a number you can add, multiply, and divide (and where addition is just XOR, which is why this is fast in hardware).

Multiply that polynomial by x18, divide it by a fixed generator polynomial with 18 roots, and keep the remainder. That remainder is the EC codewords.

Now the whole transmitted block is exactly divisible by the generator. When it comes back not divisible, the pattern of the leftover — the syndrome — tells you not just that there's an error, but where it is and what it should have been. That's the whole trick. The redundancy isn't a spare copy; it's a constraint the message must satisfy, and the ways it fails to satisfy it point straight at the damage.

The block structure at M, version 4

Every codeword in the code, in the order it's written into the grid. data · error correction. Long codes are split into blocks and interleaved, so a blot that wipes out one region of the grid spreads its damage thinly across several blocks instead of killing one outright. That interleaving is a design decision about the physical world.

Buying redundancy costs room

Push the level up and watch the turquoise grow and the blue shrink — in a code of the same size. There is no free lunch: at level H roughly 30% of the code is EC, so your string has to fit in what's left, and it may need a bigger version to do it.

leveldata cwEC cwbyte capacityyour 53 bytes

Capacities shown for the version each level would actually choose for your current string.

4 · the demo

Now break it. Then scan it.

This is the part to do with your phone in your hand. Knock modules out of the code and scan it anyway. It will keep working long past the point where it looks ruined — and then, at a boundary you can find with your own fingers, it will stop.

Undamaged. Drag the slider.
Do this now, before you read on: set the level to H, leave the style on blot, and drag the slider to 20%. A fifth of the payload is gone — there's a hole punched clean through the middle of it. Now point your phone at the screen. It reads instantly.

Then keep dragging. Somewhere past 25% it dies. Find that edge with your fingers — that's the lesson, and it's the only part of this page you can't get from reading.

And note: the verdict box is my model. Your phone is the truth. If they ever disagree, believe the phone and come tell me.

Where the cliff is, and why

The counter above isn't a guess. This page knows exactly which codeword every module belongs to — it recorded that while writing the data into the grid — so when you knock out a module it marks that codeword as damaged, and compares the count against the real budget.

The budget is unforgiving and specific. A block with e EC codewords can repair ⌊e / 2⌋ damaged codewords — half, not all. Why half? Because the decoder doesn't know where the errors are. Each error costs two codewords of budget: one to locate it, one to correct it. (Tell a decoder where the damage is — an erasure — and it repairs twice as many. Your phone doesn't get that hint.)

So at level H, version 4: four blocks, 16 EC codewords each, 8 repairable per block — 32 of 100 codewords. That's where "~30%" comes from. It was never a vibe; it's ⌊16/2⌋ × 4.

☕ Why a coffee ring beats confetti

Run this A/B. Level H, slider at 10%, then flip the style toggle back and forth. Same string, same 10% of modules destroyed, and with the join URL you'll see:

blot     25 codewords hit · scans
confetti 100 codewords hit · dead

Four times the carnage from the same amount of damage. A codeword is 8 modules sitting in a huddle, and it is equally dead whether you break one bit of it or all eight. A blot destroys 8 modules that were mostly the same codeword; confetti ruins 8 different ones. So concentrated damage is cheap and scattered damage is ruinous — the exact opposite of the intuition. It's also why real QR codes shrug off a coffee ring, a staple, or a logo, and why "survives 30% damage" is only true of the kind of damage the physical world actually produces.

✖ Why the finder is different

Destroy a finder and the counter says the payload is fine — and your phone still can't read it. Reed–Solomon protects the message. It does nothing for the frame. If the phone can't locate and orient the grid, it never gets as far as asking what the modules say. Not all damage is equal: you can lose a third of the payload and be fine, or lose one 7×7 bullseye and be dead. That asymmetry is the reason the logo in the middle of a fancy QR code is in the middle, and never in a corner.

5 · why it matters

One idea, doing all of it.

Reed–Solomon was published in 1960, five years before anyone could build hardware fast enough to run it. Irving Reed and Gustave Solomon were at MIT Lincoln Lab; the paper is eight pages long.

The Voyager probes carry it. A signal from beyond the heliopause arrives at Earth fainter than a refrigerator bulb seen across the solar system, and it arrives correct — not because the noise was avoided, but because the message was built to survive it. Same for CDs and DVDs: the reason a scratched disc plays is a Reed–Solomon code interleaved across the physical surface, on the assumption that scratches are lines and lines are concentrated damage. The same assumption you just tested with the coffee ring.

It's in DSL, in digital TV, in RAID arrays, in the QR code on a package on your porch, and it's in the code you scanned to get into the room. One idea: add structured redundancy so a message survives noise. Not a backup copy — a mathematical constraint that damage can't hide from.

For the classroom: this whole page is one HTML file with no dependencies — the encoder is ~450 lines of vanilla JavaScript you can read in one sitting. The interesting student question isn't "how do I make a QR code" (a library does that in one line). It's "how much can I break it before it dies, and can I predict that number before I test it?" That's a lab: form a hypothesis from ⌊e/2⌋, damage the code, scan it, and find out whether the arithmetic was right. The prediction is the assignment. The phone is the grader.