Why Your QR Code Looks Too Dense — featured illustration
Technology

Why Your QR Code Looks Too Dense

6 min read
Create a QR code

A QR code that comes back as a thicket of tiny squares is reporting one fact: the string inside it is long. Density is not a style, not a quality setting and not something a generator can dial down on request. It is the output of three inputs, and in practice only the first is yours to move.

The complaint usually arrives with a printed proof attached. The code looked fine in the layout at screen size, and on paper at 20mm it is a grey texture that two phones out of three refuse. Nobody changed the design between those two moments. Somebody added parameters to the link.

Density is an output, not an option

An encoder takes your content, works out which mode it can be packed in, counts the bits, adds the check data the error correction level demands, and picks the smallest version where the whole thing fits. Version 1 is 21 modules across, version 2 is 25, and every step after that adds four more per side, up to 177 at version 40. The version ladder sets out what else changes on the way up.

There is no point in that sequence where a preference for a sparser-looking symbol could be applied. Shorten the content and the version comes down. Leave the content alone and it will not, whatever you do to the artwork, the colours or the printed size.

What a tracking URL costs, counted in modules

Numbers settle this faster than argument. Our generator fixes error correction at level H, so read the H column.

A short redirect link of around 25 characters lands in byte mode and fits version 4, which holds 34 bytes at level H. That is a 33 by 33 grid. Add a full set of campaign parameters and the same link runs to roughly 110 characters, which needs version 10 at 119 bytes. That is 57 by 57.

Same printed square, two very different objects. At a 30mm symbol width the first gives each module 30 divided by 33, about 0.91mm. The second gives 30 divided by 57, about 0.53mm. The design did not change. The string got longer, and every module got narrower to make room for it.

That is why campaign parameters are the most expensive thing most people put into a printed code. Lowercase letters, question marks, equals signs and underscores all sit outside the alphanumeric character set, so a URL is packed at a full byte per character with no compression trick available to it. A hundred extra characters of tracking is a hundred extra bytes, and bytes are what push the version up.

Level H is part of the answer here

Error correction is not a layer bolted onto the side of a symbol. It lives in the same modules the data does, so a higher level leaves less room for content at any given version. Our generator uses level H with no control over it, which is a deliberate trade: H is the level that tolerates a logo in the centre and a surface that gets handled.

The cost shows up as density. That same 110-character URL fits version 7 at level M, which holds 122 bytes and gives a 45 by 45 grid. At level H it needs version 10 and 57 by 57. Identical string, three versions apart, purely because a larger share of each symbol is check data rather than payload. The four levels and what each one recovers covers the trade in general terms. The practical reading for a code that looks too fine is that the payload is the lever available to you, not the level.

Why a redirect produces a visibly sparser symbol

Put a 200-character destination behind a redirect and the symbol does not encode 200 characters. It encodes the redirect, which is short and stays short however the destination grows. The campaign parameters, the session identifiers and the deep-link payload all live on the far side of that hop, where length costs nothing.

So a sparse code and a dense code can point at exactly the same page. One carries the address; the other carries a pointer to it. That is the largest single lever on density available to anyone printing small, and it is what a dynamic code is mechanically for. Measurement survives the move, because the redirect counts the scan as it passes through, so the parameters never needed to be in the ink to be counted.

The print consequence is module size, not code size

Enlarging the print does not reduce the module count. It raises the module size, which is the number that decides whether a camera can read the symbol at all, and layouts rarely have the room to grow.

One division gets you the answer. Take the printed width of the symbol, excluding the quiet zone, and divide it by the module count across. Below roughly 0.4mm per module most phone cameras begin to fail, and that floor rises on absorbent stock, at low print resolution, and in poor light.

Run the two earlier examples through three widths:

  • Version 4, 33 modules across: 0.61mm at 20mm wide, 0.76mm at 25mm, 0.91mm at 30mm.
  • Version 10, 57 modules across: 0.35mm at 20mm wide, 0.44mm at 25mm, 0.53mm at 30mm.

The 110-character URL at 20mm is already under the floor before it reaches a press. Put it on uncoated stock and ink spread narrows the light gaps between neighbouring dark modules until some of them close, which is the failure that looks like a smudge and reads as nothing. In raster workflows the equivalent floor is about four printed pixels per module, which is where resolution for print picks the question up.

Shortening the payload, in the order that pays

  1. Move tracking parameters to the destination. The biggest single cut available, and it changes nothing about what you can measure.
  2. Shorten the host and the path. A short domain with a three-character slug beats a folder-deep URL on the main site.
  3. Delete what nobody reads: session identifiers, source tags already present in the path, prefilled form values, default parameters that match the default anyway.
  4. On a contact card, drop the photo, the note and the second website. A photo pushes a vCard toward the format's ceiling and off a business card entirely.
  5. Only then grow the printed size, and check the result against reading distance rather than against the gap in the layout. The size guide has those numbers.

The one-minute diagnosis

Count the modules along the top edge of the symbol, from the outer edge of one corner square to the outer edge of the other, ignoring the margin. Subtract 17 and divide by four, and you have the version. Then measure the printed width of the symbol and divide by the module count.

If the answer sits comfortably above half a millimetre, the code is not too dense and something else is wrong: glare, contrast, a clipped margin, a dead destination. If it comes out under 0.4mm, you have found the problem, and the string is where the fix lives.

Share this article