Skip to main content

compression

How File Compression Works: Huffman, LZ77, and DEFLATE Explained

robinhood-projects13 min read

Compress PDF β€” Free Online

Reduce your PDF file size in seconds. Choose Low, Medium, or High quality β€” text and vectors stay sharp, only embedded images are downsampled. No signup required.

Use Tool β†’

Extract ZIP

Open and extract files from ZIP, 7Z, TAR.GZ, and RAR archives

Use Tool β†’

How File Compression Actually Works: Huffman Coding, LZ77, and DEFLATE Explained

Every developer has worked with compressed files. You unzip a package, serve a gzip-encoded response, or export a PNG β€” and compression is happening under the hood. But the question most tutorials skip is: why does it work? Why can you take a 500 KB text file and reduce it to 180 KB without losing a single byte, but you cannot meaningfully compress a ZIP file further?

The answer lies in information theory and a handful of elegant algorithms. This article walks through the real mechanics: from Shannon entropy to Huffman trees to the DEFLATE format that underpins ZIP, gzip, and PNG.


Why Files Are Compressible at All

Files are compressible because they contain redundancy β€” patterns, repetitions, and predictable sequences that carry less information than their raw byte count suggests.

Consider a simple example. The string AAAAAAAAAA (ten As) takes 10 bytes in ASCII. But the information content is trivial: you could describe it perfectly as "ten As." A compression algorithm does exactly that β€” it replaces verbose, redundant data with a shorter description.

Real files are full of this kind of redundancy:

  • English text re-uses common words and letter combinations constantly. "the", "ing", "tion" appear far more often than "xqz".
  • Source code has deeply repetitive structure: function, return, const, indentation runs, blank lines.
  • Bitmap images contain long horizontal runs of identical or near-identical pixels β€” especially in flat-color areas.
  • Log files repeat timestamps, hostnames, and status codes thousands of times.

The more redundant the data, the more compressible it is. The less redundant β€” the more random β€” the harder it becomes to compress.


Entropy: The Hard Limit on Compression

Claude Shannon's 1948 paper A Mathematical Theory of Communication introduced a precise way to measure information content: entropy, measured in bits.

Shannon entropy answers the question: how many bits do you actually need, on average, to represent one symbol from this data source?

The formula for a source with symbols s₁, sβ‚‚, ..., sβ‚™ and probabilities p₁, pβ‚‚, ..., pβ‚™ is:

H = -Ξ£ pα΅’ Γ— logβ‚‚(pα΅’)

Here is what that means in practice. Suppose you have a file containing only two characters: A (appears 90% of the time) and B (appears 10% of the time).

H = -(0.9 Γ— logβ‚‚(0.9)) - (0.1 Γ— logβ‚‚(0.1))
H = -(0.9 Γ— -0.152) - (0.1 Γ— -3.322)
H β‰ˆ 0.137 + 0.332
H β‰ˆ 0.469 bits per symbol

A naive encoding uses 1 bit per symbol (0 for A, 1 for B). But the entropy says the theoretical minimum is 0.469 bits per symbol β€” less than half. A good compression algorithm gets close to that limit.

Now consider a truly random file where every byte value (0–255) appears with equal probability (1/256 each):

H = -(256 Γ— (1/256) Γ— logβ‚‚(1/256))
H = logβ‚‚(256) = 8 bits per byte

Eight bits per byte β€” full entropy. There is no redundancy to exploit. This is why you cannot compress a ZIP file further: it is already close to maximum entropy. Every bit is carrying information; there are no patterns left to eliminate.


Run-Length Encoding: The Simplest Form

Before getting to the heavier algorithms, run-length encoding (RLE) illustrates the core idea cleanly.

RLE replaces a run of repeated symbols with a count and the symbol:

Original:  AAABBBBBCCDDDDDD
RLE:       3A 5B 2C 6D

Original: 16 characters. Encoded: 8 characters. 50% reduction.

RLE is used in fax transmission (CCITT Group 3/4), BMP files, and as a sub-step inside more complex formats. Its weakness is obvious: it performs poorly on data without long runs. The string ABABABAB has no runs, so RLE makes it larger by prepending counts of 1 everywhere.

That weakness is exactly what drives more sophisticated approaches.


Huffman Coding: Shorter Codes for Common Symbols

In standard ASCII, every character gets 8 bits regardless of how often it appears. Huffman coding breaks that assumption. Frequent symbols get shorter codes; rare symbols get longer codes. The average code length drops, and the file shrinks.

Building a Huffman Tree

Take the string AABBBCCCC (9 characters). Count the frequencies:

SymbolCountProbability
C44/9
B33/9
A22/9

Step 1. Put each symbol in a leaf node weighted by its frequency.

[A:2]  [B:3]  [C:4]

Step 2. Repeatedly merge the two lowest-weight nodes into a parent node whose weight is their sum, until one tree remains.

Merge [A:2] and [B:3] β†’ parent [AB:5]:

      [AB:5]
      /     \
   [A:2]  [B:3]

[C:4]

Merge [C:4] and [AB:5] β†’ root [ABC:9]:

         [ABC:9]
         /      \
      [C:4]  [AB:5]
              /    \
           [A:2]  [B:3]

Step 3. Assign codes by traversing the tree: left edge = 0, right edge = 1.

SymbolPathCodeLength
Cleft01 bit
Aright β†’ left102 bits
Bright β†’ right112 bits

Counting the Bit Savings

With Huffman codes, encoding AABBBCCCC costs:

A Γ— 2 occurrences Γ— 2 bits = 4 bits
B Γ— 3 occurrences Γ— 2 bits = 6 bits
C Γ— 4 occurrences Γ— 1 bit  = 4 bits
Total = 14 bits

With fixed 8-bit ASCII:

9 characters Γ— 8 bits = 72 bits

14 bits vs 72 bits β€” an 81% reduction on this toy example. Real-world gains are smaller, but the principle holds. English text typically compresses to 4–5 bits per character with Huffman alone, down from 8.

The critical insight: Huffman codes are prefix-free. No code is a prefix of another, which means a decoder can unambiguously reconstruct the original stream without any separators. The tree structure itself is stored in the compressed file header so the decoder can rebuild it.


LZ77 and LZ78: Exploiting Repeated Sequences

Huffman coding works on individual symbol frequencies. But it misses a larger class of redundancy: repeated sequences. The word "function" in source code carries 8 bytes each time it appears. If you could replace the second and subsequent occurrences with a pointer back to the first, you would save significantly.

That is exactly what Abraham Lempel and Jacob Ziv described in their 1977 and 1978 papers, giving us LZ77 and LZ78.

LZ77: Sliding Window Compression

LZ77 maintains a sliding window β€” a buffer of recently seen data. As the encoder processes new data, it looks back through the window to find the longest match for what comes next. If it finds one, it emits a back-reference (offset, length) instead of the literal bytes.

Consider encoding the string:

abcabc

Processing abc: no prior history, emit literals a, b, c.

Processing the second abc: the encoder looks back and finds abc starting 3 positions ago, with length 3. It emits the back-reference (offset=3, length=3) instead of three literal bytes.

The decoder, seeing (3, 3), goes back 3 positions in its output buffer and copies 3 bytes β€” reconstructing abc perfectly.

In a real implementation, the match search is bounded by the window size (gzip uses a 32 KB window). A longer window finds more matches but costs more memory and search time.

The back-reference (offset, length) is typically 2–3 bytes. So replacing a 10-byte repeated sequence with a 2-byte reference saves 8 bytes. The longer the match, the better the ratio.

LZ78 and LZW

LZ78 builds an explicit dictionary of encountered patterns rather than using a sliding window. LZW (Lempel-Ziv-Welch), a derivative, became famous as the algorithm behind GIF compression and the Unix compress utility. LZW encodes patterns as single dictionary indices, growing the dictionary on the fly during encoding and decoding.


DEFLATE: LZ77 Plus Huffman Coding

DEFLATE, defined in RFC 1951, combines LZ77 and Huffman coding in sequence:

  1. LZ77 pass: Replace repeated sequences with (offset, length) back-references. The output is a stream of literals and back-references.
  2. Huffman pass: Apply Huffman coding to that stream. Frequent literals get short codes; rare back-reference values get longer ones.

The two techniques attack different types of redundancy. LZ77 eliminates long-range repetition. Huffman coding then eliminates frequency-based redundancy in whatever remains. Together they achieve compression ratios that neither alone could reach.

DEFLATE data is organized into blocks. Each block can use one of three modes:

  • No compression β€” for data where compression would expand the output (already-random data)
  • Fixed Huffman codes β€” a pre-agreed code table, saving the overhead of storing a custom tree
  • Dynamic Huffman codes β€” a custom tree optimized for this specific block, stored in the block header

The dynamic mode is used for most compressible data. The encoder analyzes the block, builds an optimal Huffman tree, stores it at the start of the block, then encodes the data using that tree.


Where DEFLATE Lives in Real File Formats

ZIP

ZIP files store each entry independently with its own DEFLATE-compressed stream. This per-entry compression means you can extract a single file from a ZIP without decompressing the whole archive. The ZIP format also supports a "stored" mode (no compression) for files that are already compressed.

gzip

gzip wraps a single DEFLATE stream with a header (magic number, OS, modification time) and a CRC32 checksum. It is the standard compression format for HTTP Content-Encoding: gzip and for .tar.gz archives. The .gz extension is always a single DEFLATE stream.

PNG

PNG uses DEFLATE (via zlib) to compress its pixel data. Before compression, PNG applies a filter step: it transforms each row of pixels into differences from a prediction (delta coding). This dramatically increases the runs and repetitions that LZ77 can exploit, improving compression significantly. For typical screenshots and diagrams, PNG compresses to 20–50% of the raw pixel data size. Image compression tools that export PNG are relying on this pipeline.

zlib

zlib is a wrapper format around DEFLATE β€” it adds a two-byte header and an Adler-32 checksum. PDF streams use zlib/DEFLATE when compressing embedded content (PDF compression reduces file size by recompressing or removing these streams). HTTP, WebSockets, and many network protocols also use zlib.

JPEG: A Different Approach

It is worth pausing here because JPEG uses a completely different compression strategy β€” one that deliberately loses data.

JPEG applies a Discrete Cosine Transform (DCT) to 8Γ—8 pixel blocks. The DCT converts pixel values into frequency components (similar to a Fourier transform). High-frequency components β€” fine detail β€” are then quantized aggressively (rounded to coarser values or discarded entirely). The remaining coefficients are encoded with Huffman coding, but the lossy quantization step is what makes JPEG so much smaller than PNG for photographs.

The trade-off: JPEG cannot perfectly reconstruct the original pixels. PNG can. This is the lossless vs. lossy distinction explored in depth in our article on lossy vs. lossless file conversion.

For a photograph, JPEG at quality 80 might use 10% of the bytes a lossless PNG would need. For a screenshot with flat colors and sharp edges, PNG usually wins because DCT artifacts are visible and DEFLATE compresses flat regions extremely well.


Why You Cannot Compress a ZIP File Further

This is a common source of confusion. If compression works by finding patterns, why does compressing an already-compressed file yield almost nothing?

Because DEFLATE is doing its job correctly. A well-compressed DEFLATE stream looks statistically random β€” by design. The back-references and Huffman codes eliminate the predictable patterns. What remains is close to the entropy limit of the data.

Run a second compressor over it and it finds no runs (LZ77 finds no long matches) and no frequency skew (Huffman sees a near-uniform symbol distribution). The entropy is already 7.9+ bits per byte. There is nothing left to exploit.

This is also why you should never compress already-compressed media. Wrapping a JPEG inside a ZIP costs you the ZIP overhead with no size benefit. The same applies to MP3, MP4, HEIC β€” all compressed formats that already sit near their entropy ceiling.


Algorithm Comparison

AlgorithmTypeExploitsUsed InTypical Ratio
RLELosslessRepeated runsBMP, fax, PCXVaries wildly
Huffman codingLosslessSymbol frequencyDEFLATE, JPEG DC, MP3~2:1 on text
LZ77LosslessRepeated sequencesDEFLATE, gzip~2–4:1 on text
LZ78 / LZWLosslessDictionary patternsGIF, TIFF, old Unix compress~2–3:1 on text
DEFLATE (LZ77 + Huffman)LosslessFrequency + repetitionZIP, gzip, PNG, PDF, zlib~3–5:1 on text
DCT + Huffman (JPEG)LossyPerceptual frequencyJPEG, MJPEG~10–20:1 on photos
LZMALosslessLarge-window LZ + range coding7-Zip, xz, Android APK~4–8:1 on text

Putting It Together

The chain from raw data to compressed bytes looks like this for a gzip-compressed file:

  1. Input: Raw byte stream (e.g., a text file)
  2. LZ77: Scan for back-references with a 32 KB sliding window, producing a stream of literals and (offset, length) pairs
  3. Huffman coding: Build two trees β€” one for literals/lengths, one for offsets β€” and encode the LZ77 output
  4. DEFLATE blocks: Package the Huffman trees and encoded data into one or more blocks
  5. gzip wrapper: Prepend the gzip header, append the CRC32 and original file size
  6. Output: .gz file, typically 30–70% smaller for compressible input

Decompression runs strictly in reverse: parse the gzip header, read and reconstruct Huffman trees from block headers, decode the Huffman stream back into LZ77 tokens, apply back-references to reconstruct the original byte stream. Decompression is always faster than compression because the hard work (searching for optimal matches, building optimal trees) is done only once.


Key Takeaways

  • Files are compressible because they contain redundancy β€” repeated patterns and skewed symbol frequencies.
  • Shannon entropy sets the hard floor: truly random data cannot be compressed because every bit is already carrying information.
  • RLE handles repetition crudely. Huffman coding handles frequency skew elegantly. LZ77 handles long-range sequence repetition.
  • DEFLATE chains LZ77 and Huffman in sequence, which is why it beats either algorithm alone. It is the engine inside ZIP, gzip, PNG, and zlib-compressed PDF streams.
  • JPEG takes a fundamentally different approach: lossy DCT quantization followed by lossless Huffman, trading pixel accuracy for dramatic size reduction.
  • Compressing a compressed file accomplishes nothing because good compression produces near-maximum-entropy output.

Understanding these algorithms matters when you are choosing a format, diagnosing unexpectedly large files, or debugging a compression pipeline. The next time you reach for PDF compression, extract a ZIP file, or export a PNG for image compression, you will know exactly what the software is doing to your bits.