You've zipped a folder a thousand times. But what's actually happening inside that .zip file? It's not one trick — it's two classic algorithms stacked on top of each other, and understanding them makes you better at reasoning about performance, file sizes, and when compression will (and won't) help you.
Let's open the hood.
The two-stage pipeline: DEFLATE
Almost every ZIP file uses an algorithm called DEFLATE, which itself is a combination of two separate compression techniques run back to back:
LZ77 — removes repetition
Huffman coding — removes statistical redundancy
Each stage attacks a different kind of waste in your data. Together, they cover most of what makes text, code, and structured files compressible at all.
Stage 1: LZ77 — finding repeated patterns
Most real-world files are full of repetition. Source code repeats keywords and variable names. English text repeats common words. LZ77 exploits this by replacing repeated sequences with a back-reference instead of storing them again.
Concretely, LZ77 slides a window over your data and, whenever it spots a sequence that already appeared recently, it replaces it with a triplet:
(distance, length, next_literal)
Take this (deliberately repetitive) string:
the cat sat on the mat, the cat ran
The second occurrence of "the cat" doesn't need to be stored character by character — LZ77 can just say "go back 20 characters, copy 7 characters." A tiny pointer replaces a chunk of text.
A simplified version of the core idea in JavaScript:
function findLongestMatch(data, pos, windowSize = 32) {
let bestLength = 0;
let bestDistance = 0;
const start = Math.max(0, pos - windowSize);
for (let i = start; i < pos; i++) {
let length = 0;
while (
pos + length < data.length &&
data[i + length] === data[pos + length]
) {
length++;
}
if (length > bestLength) {
bestLength = length;
bestDistance = pos - i;
}
}
return { distance: bestDistance, length: bestLength };
}
This is the naive O(n·windowSize) version — real implementations (like zlib) use hash chains to find matches far faster, but the underlying logic is exactly this: look back, find the longest match, replace it with a pointer.
Stage 2: Huffman coding — squeezing the leftovers
After LZ77 removes repetition, you're left with a stream of literals and back-references. Some of those symbols show up far more often than others — and that's exactly what Huffman coding exploits.
Standard text encoding (like ASCII) gives every character a fixed number of bits, regardless of how common it is. Huffman coding throws that out and assigns shorter bit-codes to frequent symbols and longer codes to rare ones — built from a binary tree where common characters sit near the root.
Example: if e and t appear constantly in your file but q and z barely show up, Huffman coding might represent e in 3 bits and q in 9 bits, instead of giving both a flat 8 bits. Across a whole file, that adds up fast.
This is why DEFLATE runs Huffman after LZ77 — LZ77's output (a mix of literals and distance/length pairs) still has an uneven symbol frequency distribution that Huffman can exploit further.
Why some files barely compress at all
This two-stage model explains a question devs ask constantly: "Why doesn't zipping my already-compressed files (JPEGs, MP4s, another ZIP) shrink them much?"
Both LZ77 and Huffman coding rely on redundancy — repeated patterns and uneven symbol frequency. Formats like JPEG and MP4 are already compressed, which means their output is close to random-looking, high-entropy data with very little repetition left to exploit. Compressing already-compressed data is close to compressing noise — there's nothing left for LZ77 to find or Huffman to reweight.
This is also why plain text, source code, JSON, CSV, and uncompressed formats (BMP, WAV) compress dramatically better than images or video — they're full of exactly the redundancy DEFLATE is designed to eliminate.
Client-side (browser) ZIP compression
Traditionally, compression happened entirely on a server: you upload a file, a backend process runs zlib or similar, and you download the result. Modern browsers can now do this entirely client-side, using either:
The native Compression Streams API (CompressionStream('deflate')), supported in most modern browsers, or
JS libraries like pako (a port of zlib to WebAssembly/JS) or JSZip for building actual .zip archives with headers, directory structure, and metadata
A minimal example using the native API:
async function compressFile(file) {
const stream = file.stream().pipeThrough(new CompressionStream('deflate'));
const compressedBlob = await new Response(stream).blob();
return compressedBlob;
}
The practical upside of doing this in-browser rather than server-side: the file never leaves the user's device. For anyone compressing sensitive documents, that's a meaningful privacy difference, not just a convenience one — it's the same principle behind tools like compress zip file, which handles ZIP compression directly in the browser instead of routing files through a server.
The takeaway
ZIP compression isn't one algorithm — it's LZ77 eliminating repeated sequences, followed by Huffman coding eliminating statistical redundancy in what's left. Once you see it as that two-stage pipeline, a lot of practical compression behavior — why text compresses well, why media files don't, why "zipping a zip" does nothing — stops being a mystery and starts being predictable.
Top comments (0)