Why base64 costs exactly one third
Base64 exists to move arbitrary bytes through channels that only accept text. Email bodies, URLs, JSON strings, XML attributes and HTML documents are all text, and a byte with value 0x00 or 0x1B has no business in any of them. The encoding maps binary onto 64 characters that survive every reasonable transport: A–Z, a–z, 0–9 and two symbols.
Sixty-four characters is 26, so each character carries exactly six bits. Three input bytes are 24 bits, which divides evenly into four six-bit symbols. That is the entire design: 3 bytes in, 4 characters out, and since each character is transmitted as one ASCII byte, the output is 4/3 of the input. One third more, or 33.33% overhead — a constant, not an estimate.
When the input is not a multiple of three, the final group is short and the encoder pads. One leftover byte is 8 bits, which fills two six-bit symbols with four bits to spare, so two = characters are appended. Two leftover bytes are 16 bits, filling three symbols, so one = is appended. The padding carries no data; it exists so a decoder reading a concatenated stream knows where the last group ended and how many bytes it held.
The other encodings in this calculator are the same idea at different ratios. Base32 uses 32 characters, so five bits each; five bytes (40 bits) become eight characters, a ratio of 8/5 and 60% overhead — the price of an alphabet that survives case-insensitive handling and being read aloud. Hex uses 16 characters, four bits each, so every byte becomes exactly two characters: 100% overhead, no padding, and no group arithmetic at all.
The exact formulas, including the ceiling functions
With padding: L = 4 × ⌈n/3⌉. Count the groups, rounding up, and multiply by four. This is always a multiple of four, which is what a strict decoder expects.
Without padding: L = ⌈4n/3⌉. Count only the characters that carry data. For n ≡ 1 (mod 3) this is two characters into the final group; for n ≡ 2 (mod 3) it is three.
The difference between the two is the padding count: 0 when n is a multiple of 3, 2 when the remainder is 1, and 1 when the remainder is 2. Note the inversion — one leftover byte produces two padding characters and two leftover bytes produce one — which is a common source of off-by-one errors when people write the arithmetic from memory.
MIME wrapping. RFC 2045 requires encoded bodies to be broken into lines of no more than 76 characters, each terminated by a CRLF. That is two extra bytes per line, so the wrapped size is L + 2⌈L/76⌉. On a large payload this adds a further 2.63% — 2 bytes per 76, so 2/76 = 0.0263 — bringing the total overhead from 33.33% to about 36.84%. Implementations differ on whether the final line carries a terminator; this calculator counts one, so a tool that omits it produces output two bytes shorter.
Data URIs. A data URI is data: + the MIME type + ;base64, + the payload. The fixed part is 5 + 8 = 13 characters plus the length of the MIME type, so data:image/png;base64, is 22 characters. That prefix is negligible for an image and is not negligible for a 20-byte inline SVG fragment, where it can exceed the payload.
Decoding. Going the other way, n = 3L/4 for base64, with the exact figure reduced by one byte per padding character. The calculator's reverse output floors the division, which gives the maximum bytes an encoded length of that size can hold.
Worked example: inlining a 100 KB PNG as a data URI
You have a 100,000-byte PNG and you are deciding whether to inline it in a stylesheet as a data URI.
- Groups. ⌈100,000 ÷ 3⌉ = 33,334, because 3 × 33,333 = 99,999 leaves one byte over.
- Padded length. 33,334 × 4 = 133,336 characters.
- Unpadded length. ⌈400,000 ÷ 3⌉ = ⌈133,333.33⌉ = 133,334 characters.
- Padding. 133,336 − 133,334 = 2 characters, consistent with a remainder of one byte.
- Overhead. 133,336 − 100,000 = 33,336 bytes, which is 33,336 ÷ 100,000 = 33.336%. Slightly above the asymptotic 33.333% because of the padding.
- Data URI.
data:image/png;base64,is 22 characters, so the whole URI is 22 + 133,336 = 133,358 characters. - If it were an email attachment. Wrapping at 76 characters gives ⌈133,336 ÷ 76⌉ = 1,755 lines and 3,510 bytes of CRLF, for a total of 136,846 bytes — an overhead of 36.846%.
Compare the alternatives on the same 100,000 bytes. Hex would produce 200,000 characters. Base32 would produce 8 × ⌈100,000 ÷ 5⌉ = 8 × 20,000 = 160,000 characters exactly, with no padding because 100,000 divides by five. A separate binary file would transfer 100,000 bytes plus one HTTP request. The inline version saves the request and costs 33,358 extra bytes every time the stylesheet is fetched and cannot be cached separately from it.
What to do with the overhead figure
Judge the 33% against what it buys. For a small icon inlined in CSS, saving a round trip is usually worth a third more bytes, especially on a high-latency connection where the request costs more time than the payload. For anything large, the calculation reverses: a 2 MB image inlined into a document adds 667 KB, is re-downloaded whenever the document changes, and cannot be cached, served from a CDN or lazily loaded on its own.
Check whether compression recovers the overhead. Base64 output is highly structured text, and HTTP transports normally apply gzip or Brotli. On base64-encoded data that is already compressed — a PNG, a JPEG, a ZIP — the compressor finds little to remove, and you keep most of the third. On base64-encoded text or uncompressed data, the compressor typically recovers a large part of it. The rule of thumb worth carrying is that base64 plus gzip is close to raw plus gzip for compressible content and close to raw plus a third for incompressible content.
Watch the multiplication factor in JSON APIs. A response containing many base64 fields pays the overhead on each, and if the payload is then stored in a database as text and indexed, the cost repeats in storage and in memory. Where an API returns binary regularly, a separate endpoint that serves the bytes directly is usually the right design, with the base64 field reserved for small values such as cryptographic digests.
Do not use base64 as a security measure. It is an encoding, not encryption: anyone can decode it in one line, and it is fully reversible by design. If the payload needs protecting, encrypt it and then encode the ciphertext — in that order — and size the key with the key brute-force time calculator.
Finally, if the encoded blob crosses a network you are budgeting for, feed the encoded figure rather than the raw one into the data transfer time calculator, and remember that packet headers add their own overhead on top — quantified by the MTU and MSS overhead calculator.
Encoded length for small inputs, and overhead by encoding
| Input bytes | Padded chars | Unpadded chars | Padding | Overhead (padded) |
|---|---|---|---|---|
| 1 | 4 | 2 | 2 | 300% |
| 2 | 4 | 3 | 1 | 100% |
| 3 | 4 | 4 | 0 | 33.3% |
| 4 | 8 | 6 | 2 | 100% |
| 5 | 8 | 7 | 1 | 60% |
| 6 | 8 | 8 | 0 | 33.3% |
| 10 | 16 | 14 | 2 | 60% |
| 100 | 136 | 134 | 2 | 36% |
| 1,000 | 1,336 | 1,334 | 2 | 33.6% |
| 100,000 | 133,336 | 133,334 | 2 | 33.336% |
The overhead only approaches 33.33% once the input is large enough for the padding to be lost in the rounding. On very short values — a 16-byte key, a 4-byte counter — the padded overhead is materially higher, which is one reason token formats such as JWT drop the padding.
Overhead by encoding family
| Encoding | Input group | Output group | Chars per byte | Overhead | 100,000 bytes becomes |
|---|---|---|---|---|---|
| Ascii85 / base85 | 4 bytes | 5 chars | 1.25 | 25% | 125,000 chars |
| base64 and base64url | 3 bytes | 4 chars | 1.333 | 33.3% | 133,336 chars |
| base32 | 5 bytes | 8 chars | 1.6 | 60% | 160,000 chars |
| base16 (hex) | 1 byte | 2 chars | 2 | 100% | 200,000 chars |
| quoted-printable, binary data | 1 byte | up to 3 chars | up to 3 | up to 200% | up to 300,000 chars |
Quoted-printable encodes each byte outside the printable ASCII range as an = followed by two hex digits, so it is efficient for mostly-text content and worse than hex for arbitrary binary. That is exactly why MIME defines both and lets the sender choose.
Mistakes and gotchas
- Reversing the padding rule. One leftover byte produces two
=characters; two leftover bytes produce one. The relationship is inverted from what most people remember. - Assuming base64url is shorter. It uses
-and_in place of+and/so the value survives URLs and filenames. The length is identical; only percent-encoding is avoided. - Forgetting that URLs percent-encode standard base64. Put standard base64 in a query string and
+and/become%2Band%2F, three characters each. That can add far more than the 33%, which is precisely why base64url exists. - Ignoring line breaks in email. MIME wrapping adds 2.63% on top of the encoding, and some libraries wrap by default while others do not.
- Treating base64 as compression or as security. It is neither. It always makes data larger, and it is trivially reversible.
- Double-encoding. Base64 applied twice costs 1.333² = 1.778, or 78% overhead. It happens more often than it should when a value passes through two layers that each encode defensively.
- Storing base64 in a fixed-width column sized for the raw data. A 16-byte value needs 24 characters padded or 22 unpadded, not 16.
- Counting characters rather than bytes for a non-ASCII MIME type. Data URI prefixes are ASCII in practice, but if you build one programmatically from a UTF-8 string, measure bytes.
Key terms
- Encoding quantum
- The smallest group the encoder processes as a unit — 24 bits for base64, 40 bits for base32, 8 bits for hex. Padding exists to complete a partial quantum.
- Padding character
- The = appended to fill a partial quantum. It carries no data and tells a decoder how many bytes the final group held.
- base64url
- The RFC 4648 §5 variant that replaces + and / with - and _ so the value can appear in a URL path, query string or filename without percent-encoding. Same length as standard base64.
- Data URI
- A URI that carries its content inline, of the form data:[mediatype][;base64],data. Saves a request at the cost of the encoding overhead and of any caching independence.
- Quoted-printable
- The other MIME content-transfer-encoding, which leaves printable ASCII untouched and expands everything else to three characters. Efficient for text, poor for binary.
When to encode at all
Base64 is a workaround for channels that cannot carry bytes, and modern protocols increasingly can. HTTP has always carried binary bodies; the reason base64 persists on the web is that binary cannot appear inside a JSON string, an XML attribute or a CSS declaration. Where you control both ends, a multipart request or a separate binary endpoint avoids the encoding entirely and is usually the better design.
Where the encoding is unavoidable, choose the variant that matches the channel. Use base64url in URLs, path segments and JSON Web Tokens. Use standard base64 in MIME and in most APIs. Use base32 when a human has to transcribe or read the value aloud, because its alphabet avoids case sensitivity and confusable characters — this is why TOTP secrets and onion addresses use it despite the 60% cost. Use hex for short values such as digests and MAC addresses, where the doubling does not matter and the byte-per-two-characters mapping makes the value easy to read.
Two neighbouring calculations matter when you are budgeting a payload. If the underlying data is an image, the image file size calculator gives you the pre-encoding size from pixel dimensions and bit depth. And if you are comparing a compression scheme against the encoding cost, the data compression ratio calculator converts between ratios and percentage space saved so you can see whether compressing before encoding wins back more than the third you are about to add.
