I’ve always found qr code generators particularly unsatisfying, they either generate bitmap images which are rubbish for printing and fabrication, or the ones that output svgs do it by putting a square where each pixel is so that if you want a vinyl qr code you get a bunch of pixels and hopefully none are lost from the transfer tape. This lead me to learn how they work so that I could build something better, like this.
It seems that every product visionary comes up with some complex explanation that fails to explain that QR codes are just a fancy way of writing a string that a computer can read. They do it very cleverly so that they’re both reliable and information dense which is no mean feat. They were invented in 1994 by Masahiro Hara and his team at Denso Wave, a Toyota subsidiary, to solve a mundane problem — tracking car parts.
Standard barcodes held too little data, so they designed something that could store far more and be read instantly from any angle. The three-corner finder squares were deliberately chosen to match a rectangular ratio unlikely to appear on printed packaging, avoiding false positives. The name "Quick Response" says it all.
Denso Wave held the patent but chose not to enforce it, which meant anyone could use the format freely — a big reason it became a global standard rather than a proprietary niche tool. It stayed mostly industrial and Japan-facing until smartphones made scanning effortless, and the pandemic gave it one final push into everyday life.
QR codes have several modes, the most efficient is numbers only, they can store a lot of numbers. There’s an alphanumeric mode, with uppercase letters, numbers and some symbols, perfect for urls. There’s also a byte mode for general text and a Kanji mode for Japanese characters. The alphanumeric mode gives us a feature that few are aware of, upper case qr codes are more compact than those with lower case letters because there’s an optimised mode for them.
Once the data is encoded, it gets laid out across the grid in a specific zigzag path — working its way up and down in two-column strips from the bottom-right corner. It's deliberately not a simple left-to-right, top-to-bottom arrangement, partly to spread the data around and partly to make it more resilient. Scattered throughout the grid are fixed structural elements — the big square "finder" patterns in three corners (so a scanner knows where the code is and which way up it is), alignment patterns, and timing strips. The actual data weaves around all of these.
There's also error correction baked in — QR codes use Reed-Solomon coding, which means a chunk of the grid is dedicated to redundancy. This is why you can cover or damage a good portion of a QR code and it still scans fine.
The last step is masking and here’s where it gets a bit quirky. Once all the data bits are placed, the spec defines 8 different mask patterns — things like "flip every other pixel in a checkerboard" or "flip every pixel in every third row." Each mask is XORed over the data area of the code, which just means every bit that the mask "hits" gets flipped.
Why do this? Because a QR scanner can struggle with large solid blocks of the same colour, or highly regular repeating patterns — they can confuse the reader or interfere with the structural markers. So all 8 masked versions are generated, each one is scored against a set of rules (penalising things like long runs of the same colour, or patterns that look too much like the finder squares), and whichever masked version scores best gets used as the final code. The mask ID is then stored in the code itself so the scanner knows to reverse it when reading.
I learned a lot of this from Massimo Artizzu's excellent but far more details "Let's Develop a QR Code Generator" series here on Dev.to.
The outcome is that I’ve used it to build @verevoir/qr on npm so that I can have my print quality easy to fabricate qr codes without going to a website that wants all of my personal data in return.

Top comments (0)