# Why two generators produce different QR codes

> Two generators given the same text can legally differ in version selection, error correction default, encoding-mode segmentation and mask choice — all produce valid codes that decode to the same string. A difference only signals a bug when the decoded text differs, most often from mishandled UTF-8 or stray whitespace.

Source: https://useqr.app/docs/spec/why-two-generators-produce-different-codes · Last reviewed 2026-08-21 · UseQR is free forever, MIT licensed, no signup.

---

## Different picture, same meaning

Encode the same URL in two tools and you will often get two visibly different codes. This
alarms people — reasonably, since the codes are supposed to *be* the URL — but it is the
expected consequence of the standard leaving several choices to the implementation. Each
choice changes the module pattern without changing what decodes.

The four legitimate sources of difference:

| Choice | Typical variation | Visual effect |
|---|---|---|
| **Error correction default** | L, M or Q depending on library | different density and pattern |
| **Version selection** | smallest that fits vs a padded/forced minimum | different code size |
| **Mode segmentation** | one byte-mode run vs optimised mixed segments | different bit stream, subtly different pattern |
| **Mask choice** | scoring-implementation quirks pick a different winner | completely different texture |

The last one dominates. All eight [mask patterns](/docs/spec/mask-patterns-0-to-7) are
valid, decoders read all of them, and the [penalty-scored search](/docs/spec/mask-selection-penalty-scores)
that picks between them is easy to implement with small honest divergences — a slightly
different reading of one penalty rule flips the winner, and two codes for the same data
share almost no visual texture. Each individual generator is still
[deterministic](/docs/spec/deterministic-qr-generation); determinism just does not span
implementations.

Mode segmentation is worth a concrete example: `HELLO-2024` can be encoded as one
byte-mode segment (8 bits/character) or one alphanumeric segment (5.5 bits/character), and
a sophisticated encoder may split mixed data into multiple
[mode segments](/docs/spec/data-encoding-modes) to save bits. All decode identically; the
bit streams — and thus the patterns — differ.

## When a difference IS a bug

The test is never how the codes look; it is what they decode to. **Scan both. If the
decoded strings differ at all, one generator is wrong.** The recurring culprits:

- **UTF-8 mishandling** — the classic. Some libraries truncate each character to its low
  byte unless told otherwise, silently mangling every non-ASCII character (UseQR's core
  works around exactly this in its underlying encoder by pre-expanding input to UTF-8
  octets). Symptoms and fixes: [UTF-8 and Unicode in QR codes](/docs/spec/utf-8-and-unicode-in-qr-codes).
- **Stray whitespace** — a trailing newline picked up from a file or shell pipe encodes
  invisibly and breaks URLs at the far end.
- **Helpful mutations** — tools that add `http://`, strip parameters, uppercase for
  alphanumeric mode, or route your URL through their own redirect domain (a business
  model, not a bug, but the decoded string is no longer your URL — see
  [what free actually means](/vs/what-free-actually-means-in-qr-tools)).
- **Wrong ECI/charset signalling** for non-Latin text, which decodes correctly on lenient
  scanners and as mojibake on strict ones.

Comparing images tells you nothing here; comparing decodes tells you everything. Decode
with [/scan](/scan), or use [/validate](/validate), which renders and decodes in one step
and confirms the round-trip matches your input — the discipline behind
[why you should verify](/docs/developers/why-verify-that-your-qr-code-decodes).

## Does the visual difference ever matter?

Occasionally, at the margins. A generator that defaults to error correction L produces a
sparser code that tolerates less [damage](/glossary/error-correction-level); one that
defaults to Q produces a denser code that needs more
[printed size](/docs/spec/qr-code-density-and-scan-distance). Neither is wrong — but if
you compare tools, compare them at the *same* stated settings, or you are measuring their
defaults, not their correctness.

## FAQ

### Why does the same URL give different QR codes in different tools?
Because the standard leaves error correction defaults, version selection, mode segmentation and mask scoring to the implementation. Any combination yields a valid code, and mask choice alone can change the entire visual texture. All should decode to the same string.

### Are both QR codes valid if they look different?
Almost always yes — different-looking codes for the same data are the normal result of different implementation choices. The only meaningful test is decoding both: identical decoded strings mean both codes are correct.

### How do I check if a QR generator is correct?
Scan its output and compare the decoded string byte-for-byte with your input, including any accented or non-Latin characters and the exact URL parameters. A verify tool that renders then decodes automates this round-trip.

### One generator's code is bigger for the same text. Why?
It chose a higher error correction level or a padded minimum version. Higher redundancy or forced size means more modules. Set both tools to the same error correction level before comparing sizes.

## Try it

- https://useqr.app/url
- https://useqr.app/validate
- https://useqr.app/scan
