Base64 Overhead: Why Data URIs Make Files 33% Bigger
2026-09-17 · 1040 words
Base64 makes data bigger. Not slightly bigger — a third bigger, every time, with no exceptions and no settings to tune. The number is not a quirk of any particular implementation; it falls directly out of the encoding, and understanding where it comes from makes the inlining decision much easier.
Where the third comes from
Base64 exists to represent arbitrary bytes using only characters that survive text transport. The alphabet has 64 symbols, which is 6 bits of information per character. Bytes are 8 bits. So the encoder reads 3 bytes (24 bits) at a time and writes 4 characters (4 × 6 = 24 bits).
Three bytes in, four characters out:
3 bytes → 4 chars 4 / 3 = 1.333...
That is the whole story. A 300,000-byte image becomes a 400,000-character string. The overhead is exactly 33.3%, and it applies to the payload only — the data:image/png;base64, prefix adds a further 22 characters, which matters for tiny assets and is irrelevant for large ones.
The one escape hatch is padding. If the input length is not a multiple of three, the last group is padded with = characters that carry no information, so the exact size is 4 × ceil(n / 3):
| Input bytes | Base64 characters | Overhead | |---|---|---| | 1 | 4 | +300% | | 3 | 4 | +33% | | 100 | 136 | +36% | | 1,000 | 1,336 | +33.6% | | 1,000,000 | 1,333,336 | +33.3% |
The small-input rows are the argument against inlining tiny assets for size reasons: a 1-byte file becomes 4 characters plus a 22-character prefix, and the prefix dominates.
Why gzip does not save you
On text, gzip routinely achieves 60–80% reduction, which is why the 33% figure is sometimes dismissed as irrelevant. That reasoning holds for JSON and HTML and fails completely for images.
Compression works by finding redundancy. JPEG, PNG, WebP and AVIF have already removed almost all of it — that is what those formats do — so their bytes are close to random from a compressor's point of view. Base64 encodes those incompressible bytes into a restricted 64-symbol alphabet, which adds no redundancy a compressor can exploit. The result is the worst of both worlds: the data is 33% larger and still does not compress.
You can measure this in one command:
# a real image, already compressed
base64 -w0 photo.jpg > photo.b64
ls -l photo.jpg photo.b64
gzip -9 -c photo.b64 | wc -c # barely smaller than photo.b64
The garbled case is different. If you Base64-encode a text payload — JSON, XML, a log — gzip will compress the encoded form well, sometimes better than the original, because Base64 of text still contains recognisable structure. So "does gzip fix the 33%" has two answers, and they depend entirely on whether the payload was compressible to begin with.
The cost is not only bytes
Inflating a stylesheet or an HTML document has three costs that do not show up in a file-size comparison:
- Render blocking. A data URI inside a
<style>block or a linked CSS file is part of that file. The browser cannot paint until it has parsed it, and it cannot parse it until it has downloaded it. An external image request is not on that critical path. - No independent caching. Change one byte of a stylesheet that inlines twenty icons and every visitor re-downloads all twenty. As separate files they would each have their own cache entry and their own validator.
- Parsing and memory. The bytes must be decoded and held as a string before they become an image. A 400 KB string in a stylesheet is 400 KB of text the parser must walk, and it stays in the CSSOM.
None of these are fatal. All of them are invisible if you only compare file sizes.
Where the break-even actually sits
There is no universal threshold, but the decision is narrow enough to state as rules of thumb:
- Under ~2 KB, inline it. The bytes saved by avoiding a request (headers alone are often 300–800 bytes over HTTP/1.1, less over HTTP/2) can exceed the 33%. A single-colour icon or a tiny SVG is the classic case.
- 2–10 KB, it depends on caching. A favicon or a logo that never changes can be inlined safely; something that ships weekly should not be, and an asset used on ten pages should be a file so it is downloaded once.
- Over ~10 KB, use a file. The overhead is now measured in kilobytes, there is no request to save, and you have given up caching for nothing.
- Over ~1 MB, never inline it in a document. Beyond the size penalty you are now in territory where some tools truncate long attributes and some parsers get slow.
For images specifically, there is a better question than "how big is the Base64 string": could this be an SVG? A vector logo is a few hundred bytes of path data, encodes to under a kilobyte, and is resolution-independent. Inlining that is a clear win, and it is the case where the technique earns its reputation.
Measuring it on your own site
Two numbers tell you whether inlining is helping: the transfer size of the document that contains the data URI, and the transfer size of the equivalent page with external assets. If the inlined version is 30% larger, it is 30% larger — the 33% is not recoverable by the network layer.
The measurement is easy to get wrong in one specific way: comparing the uncompressed size of the image with the compressed size of the document. Compare like with like, and remember that the data URI's bytes travel inside a response that gzip can only partially shrink. Our PNG to Base64 and JPG to Base64 pages report the output length and the overhead as you convert, which is usually enough to see immediately that a 900 KB photograph should not be an inline background image.
For the cases where you do want the string — a small icon in critical CSS, an email template that cannot reference external files, a single-file HTML export, a JSON fixture in a test — the overhead is a fair price for self-containment, and knowing the exact number keeps it a deliberate choice rather than an accident. If you are deciding between the two ways of holding an image in memory rather than in a document, data URI vs blob URL covers the other half of this trade.