
QR Mask Patterns: The 8 Rules That Keep Codes Readable
Feed the same URL to two different QR generators at the same version and the same error correction level. The three corner squares match. The timing lines running between them match. The area in the middle, where the data actually sits, can look like two unrelated images. Both codes scan. Both carry the identical string.
The difference is which mask each encoder chose, and that choice happens in a step almost nobody sees.
Raw data makes bad pictures
Encoding turns your content into a stream of codewords, appends Reed-Solomon error correction codewords, then walks that stream through the matrix in a zigzag starting at the bottom right. The placement rule is fixed and mechanical. It pays no attention to what the bits happen to say, and the ratio of data to correction codewords is also what caps capacity.
So the bits can say something unfortunate. Content with long repeating stretches can drop a field of same-colored modules into the grid. A different string produces stripes that run half the width of the symbol. The worst outcome is when the data accidentally reproduces the run-length ratio that defines a finder pattern, planted in the middle of the code where no finder belongs.
Each of those breaks a different part of the read. The scanning sequence starts by locating the three finder patterns, using them to derive orientation and module size, then sampling the grid cell by cell. Large uniform areas leave the sampling grid nothing to check itself against, so small errors in the module size estimate accumulate across the symbol. A fourth finder-shaped object makes the very first step ambiguous.
What the mask flips, and what it never touches
The encoder builds eight candidate versions of the same symbol. For each one it takes a fixed geometric pattern and XORs it against the data region: where the pattern says one, the module flips color; where it says zero, the module stays as encoded.
Function patterns are exempt. Finders, their separators, the timing lines, alignment patterns, and the format and version information areas are all excluded, which is why those parts look identical across generators.
The eight patterns are deliberately unalike. Some flip on row parity and produce horizontal banding. Some work down columns and produce vertical bands. One is a fine checkerboard. Others generate diagonals at different slopes, or coarser blocks and grids. Breadth is the point. Whatever unhelpful shape the raw data landed on, at least one of the eight is likely to break it apart.
Four penalty rules, scored on every candidate
With eight finished candidates in hand, the encoder scores each and keeps the lowest. Four rules feed the score, and each one maps onto a specific way a read can fail.
The first rule punishes long runs of one color along a row or column. A run of five picks up a base penalty, and every extra module in that run adds more on top. This is what discourages the wide blank fields.
The second rule counts 2x2 blocks of a single color and charges for each. Blocks do less damage than long runs, but they erode the local edge information a reader uses to keep its sampling grid honest.
The third rule hunts for the finder lookalike: that distinctive dark-light-dark-light-dark ratio with a wide light area on one side. It carries a far heavier weight than the other three, by roughly an order of magnitude, which tells you how the standard ranks the danger. One accidental pseudo-finder outweighs a large pile of stray blocks.
The fourth rule measures the overall balance of dark to light across the whole symbol and penalizes drift away from an even split. A symbol that comes out 70 percent dark gives an imaging sensor a harder job setting its black-white threshold, particularly in weak light or on a low-contrast print.
Notice what the scoring ignores: whether the result looks good. Penalty scores are about legibility only, and the winner is simply the candidate that scored least badly.
The mask number rides in the format information
A decoder cannot infer which mask was applied, so it has to be told. Three bits identifying the mask sit in the format information alongside the error correction level. Those bits get their own error correction and are written into the symbol twice, in two separate places beside the finder patterns.
The duplication is deliberate. Lose the format information and the entire symbol becomes unreadable no matter how intact the data region is, because the reader has no way to know which pattern to remove. Reading is the same operation as writing: apply the named mask a second time and the original bits come back.
Why two generators disagree
Most of the visual variation between tools traces back to the edges of this process. Penalty scoring produces a number and the lowest number wins, but implementations differ in how they handle ties and how carefully they follow every clause of the scoring rules. Some encoders skip evaluation altogether and hard-code a mask for speed. The output still decodes, so it is legal, but it forfeits the optimization.
Two codes from two services can therefore both be correct and look nothing alike. Comparing them module for module is not a meaningful test of either one. Scanning both is.
What this means for a styled code
Anything you place on top of a finished symbol arrives after masking. The encoder optimized the module layout with no knowledge that a logo was coming, so the error correction budget absorbs the covered modules rather than the mask routing around them. That is the real reason a logo belongs in the center at a modest size, rather than drifting toward a corner where it can clip a finder or its separator.
Color and shape treatments work the same way. Rounded corners and dot-shaped modules soften the exact edges that the first two penalty rules exist to preserve, and a low-contrast palette works against the fourth. Codes usually still read, because error correction leaves margin, but you are spending margin the mask already spent once on your behalf. Design choices in a custom code are cheap individually and expensive stacked three deep. When a styled version fails and a plain one succeeds on identical content, that stack is normally where it started, and it is worth diagnosing the failure before the file goes anywhere near a print run.


