Skip to content
UseQR
ESC

Jump to

MOVEOPEN50 places

Spec & internals

Decode a QR code by hand

To decode a QR code by hand, orient the symbol using its three finder patterns, read the 15-bit format information (XOR with 101010000010010), apply the named mask to the data region, read modules in zig-zag order, de-interleave the codewords, verify with Reed–Solomon, then parse mode indicators to recover the text.

View as MarkdownPaste this page into any AI assistant. It is plain, portable Markdown.

What you need

A printed or on-screen code, graph paper, and patience. Pick a small symbol, a version 1 (21 × 21) or version 2 (25 × 25) code, because the work grows with the square of the side. This page is the reverse of encoding "hello world" by hand; doing both once teaches you more about QR than any amount of reading.

Copy the symbol onto graph paper as a grid of dark and light cells. The side length tells you the version: modules per side = 17 + 4 × version, so 21 means version 1, 25 means version 2, and so on.

Step 1: orient and read the format information

The three finder patterns belong at top-left, top-right and bottom-left. If the empty corner is anywhere but bottom-right, rotate (or un-mirror) your copy.

The 15-bit format information runs along the edges of the top-left finder. Read it, then XOR with the fixed masking constant 101010000010010 (0x5412): the spec applies this so the format field is never all zeros. The first two resulting bits give the error correction level, the next three name the mask:

EC bits Level
01 L
00 M
11 Q
10 H

The remaining 10 bits are BCH error correction over the field itself. A second, identical copy sits split beside the other two finders: read it if the first is damaged.

Step 2: remove the mask

Every data module was XORed with a mask pattern at encode time. Apply the same pattern again to undo it: for mask 0, flip every module where (row + column) mod 2 = 0; each of the eight masks has a similar coordinate formula. Only data and error-correction modules are masked: leave finders, timing patterns, alignment patterns and the format information untouched.

Step 3: read the zig-zag

Data is stored in two-module-wide columns, starting at the bottom-right corner, running upward, then down the next pair, alternating. Within each pair read right cell then left cell. Skip every function-pattern module and skip column 6 entirely (the vertical timing pattern), the walk simply steps over it. Dark = 1, light = 0. Group the bits into 8-bit codewords.

Step 4: de-interleave and check Reed–Solomon

For version 1 there is a single block, so the codewords are already in order. Larger versions interleave codewords from multiple blocks (codeword 1 of block 1, codeword 1 of block 2, and so on), and you must deal them back out using the block table for your version and level.

A full Reed–Solomon check means computing syndromes over GF(256): all zeros confirms an undamaged code. By hand, most people (reasonably) assume a cleanly printed code is intact and move on.

Step 5: parse the bit stream

Now read the data codewords as a bit stream:

First 4 bits Meaning
0001 Numeric mode
0010 Alphanumeric mode
0100 Byte mode
1000 Kanji mode
0000 Terminator: stop

After the mode indicator comes a character count (8 bits for byte mode at versions 1–9), then the data itself, in byte mode, each 8 bits is one character. Decode until you hit the terminator; everything after it is padding (the alternating bytes 11101100 and 00010001).

If you want to check your work, point /scan at the code, or feed the payload to /validate and compare.

FAQ

How do I know which mask a QR code used?

Read the 15 format bits beside the top-left finder pattern, XOR them with 101010000010010, and take bits three to five of the result. They name one of the eight mask patterns, each defined by a simple coordinate formula.

Can you really decode a QR code without a computer?

Yes, for small versions. A version 1 code holds at most 152 data bits, and every step (unmasking, the zig-zag read, mode parsing) is pencil-and-paper arithmetic. Verifying the Reed–Solomon codewords by hand is the only genuinely tedious part.

Why do the bits I read look like random noise?

You probably forgot to remove the mask. Raw modules are XORed with a mask pattern precisely so they look balanced and noise-like; undo the mask named in the format information before reading the zig-zag.

What order are QR code bits stored in?

In two-module-wide columns starting from the bottom-right corner, running alternately up and down, right cell before left cell, skipping all function patterns and the vertical timing column. Bits group into 8-bit codewords, most significant bit first.

Try it: free, no signup