Deflate Compression: Combining LZ77 and Huffman Coding
Learn Deflate compression algorithm: how LZ77 and Huffman coding combine for high-ratio lossless compression.
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
- ✓Basic understanding of data structures (arrays, trees)
- ✓Familiarity with binary trees and priority queues
- ✓Knowledge of bitwise operations
- Deflate combines LZ77 (dictionary-based) and Huffman coding (entropy coding).
- LZ77 replaces repeated data with length-distance pairs; Huffman codes symbols with variable-length codes.
- Used in PNG, ZIP, gzip, and HTTP compression.
- Provides good compression ratio with moderate speed.
- Implementation requires careful handling of sliding window and Huffman tree construction.
Imagine you're writing a long letter and notice you keep repeating the same phrases. Deflate works like this: first, it finds repeated phrases and replaces them with a short reference (like 'see paragraph 2, line 3'). Then, it looks at how often each letter appears and gives the most common letters a short code (like 'e' becomes '0') and rare letters a longer code (like 'x' becomes '110101'). This two-step process shrinks the data significantly.
Have you ever wondered how PNG images or ZIP files achieve impressive compression ratios? The secret lies in the Deflate algorithm, a clever combination of two fundamental techniques: LZ77 (Lempel-Ziv 1977) and Huffman coding. Deflate is the backbone of many compression formats, including gzip, zlib, and PNG. It offers a good balance between compression speed and ratio, making it ideal for both storage and network transfer.
In this tutorial, you'll learn how Deflate works under the hood. We'll start with LZ77, which finds repeated sequences and replaces them with length-distance pairs. Then we'll dive into Huffman coding, which assigns variable-length codes based on symbol frequency. Finally, we'll see how these two techniques are combined in Deflate to produce a compressed stream.
We'll also explore a real-world production incident where a bug in Deflate implementation caused data corruption, and provide a debugging guide to help you troubleshoot compression issues. By the end, you'll have a solid understanding of one of the most widely used compression algorithms and be able to implement it yourself.
1. Overview of Deflate
Deflate is a lossless data compression algorithm that combines LZ77 and Huffman coding. It was designed by Phil Katz for PKZIP and later standardized in RFC 1951. The algorithm works in two stages:
- LZ77 Compression: Replaces repeated sequences of bytes with a length-distance pair referencing a previous occurrence.
- Huffman Coding: Compresses the LZ77 output (literals, lengths, distances) using variable-length codes based on symbol frequencies.
- Stored (no compression): Raw data.
- Fixed Huffman: Predefined Huffman trees (useful for small data).
- Dynamic Huffman: Custom Huffman trees optimized for the data.
The algorithm processes data in blocks, each up to 65535 bytes. Each block can use a different compression mode.
2. LZ77: Sliding Window Compression
LZ77 is a dictionary-based compression algorithm that exploits repeated patterns. It maintains a sliding window of recently seen data. When a sequence of bytes matches a previous occurrence in the window, it is replaced by a length-distance pair.
Key parameters: - Window size: Typically 32KB for Deflate. - Minimum match length: Usually 3 bytes. - Maximum match length: 258 bytes.
Algorithm: 1. Maintain a buffer of the last N bytes (the window). 2. For each position, find the longest match in the window. 3. If match length >= minimum, output a length-distance pair. 4. Otherwise, output the literal byte. 5. Slide the window forward.
Example: "abcabcabc" -> literals 'a','b','c' then length=3, distance=3 (since "abc" appears 3 bytes back).
3. Huffman Coding: Entropy Coding
Huffman coding is an entropy encoding algorithm that assigns variable-length codes to symbols based on their frequency. More frequent symbols get shorter codes, reducing overall size.
Algorithm: 1. Count frequency of each symbol. 2. Build a binary tree: repeatedly combine two least frequent nodes. 3. Assign codes: left edge = 0, right edge = 1. 4. The code for a symbol is the path from root to leaf.
- Literals and lengths (0-285)
- Distances (0-29)
- Code lengths (for dynamic Huffman)
Deflate uses a maximum code length of 15 bits. If the tree becomes deeper, it must be rebalanced (e.g., using the package-merge algorithm).
4. Combining LZ77 and Huffman in Deflate
Deflate integrates LZ77 and Huffman coding seamlessly. The LZ77 output consists of three types of symbols: - Literal bytes (0-255) - Length codes (257-285) representing match lengths - Distance codes (0-29) representing match distances
These symbols are then Huffman coded. Additionally, the lengths and distances themselves are further encoded using Huffman codes (for dynamic Huffman).
Block structure: - 3-bit header: final block flag, compression type (00=stored, 01=fixed, 10=dynamic, 11=reserved) - For dynamic Huffman: Huffman trees for literals/lengths and distances are encoded using code lengths, which are themselves Huffman coded. - Then the compressed data follows.
Example flow: 1. Input: "ababab" 2. LZ77: literal 'a', literal 'b', length=2 distance=2 (since "ab" repeats) 3. Huffman: encode literals and length/distance with variable-length codes. 4. Output: bitstream.
5. Implementation Considerations
Implementing Deflate from scratch is complex. Here are key considerations:
Sliding Window: - Use a circular buffer for the window. - Use a hash table (e.g., chained hash) to quickly find matches. zlib uses a hash chain. - Minimum match length is typically 3; shorter matches are not worth the overhead.
Huffman Tree Construction: - Use a priority queue to build the tree. - Enforce maximum code length (15 bits). Use the package-merge algorithm to limit code lengths if necessary. - For dynamic Huffman, encode the tree using code lengths (run-length encoding).
Bit-level I/O: - Deflate operates on bits, not bytes. Use a bit buffer to write bits sequentially. - Ensure proper byte alignment at block boundaries.
Testing: - Use test vectors from RFC 1951. - Compare with zlib output for random data. - Verify round-trip compression/decompression.
6. Performance Optimization
Deflate performance depends on compression level (0-9). Higher levels use more aggressive search for LZ77 matches and better Huffman optimization.
Optimization techniques: - Hash chains: Use a hash table to store positions of recent strings. zlib uses a hash chain of size 32K. - Lazy matching: Instead of taking the first match, check if a longer match exists at the next position. - Block size: Larger blocks improve compression ratio but use more memory. - Memory usage: The sliding window (32KB) and hash table (32K entries) dominate memory.
Trade-offs: - Level 1: Fastest, worst compression. - Level 6: Default, good balance. - Level 9: Slowest, best compression.
Hardware acceleration: Some CPUs support Deflate via instructions (e.g., Intel QAT).
The Corrupted PNG: A Deflate Bug in Image Processing Pipeline
- Always validate Huffman code lengths against the maximum allowed (e.g., 15 bits for Deflate).
- Use well-tested libraries (zlib) unless you have a strong reason to implement your own.
- Add integrity checks (e.g., CRC32) to detect corruption early.
- Test with edge cases: all same symbols, random data, large files.
- Monitor compression ratio anomalies as a signal of potential bugs.
python -c "import zlib; print(len(zlib.compress(b'a'*1000, level=9)))"Enable verbose logging of match lengths| File | Command / Code | Purpose |
|---|---|---|
| deflate_overview.py | data = b"Hello, world! Hello, world! This is a test." | 1. Overview of Deflate |
| lz77_simple.py | def lz77_compress(data, window_size=256, min_match=3): | 2. LZ77 |
| huffman_simple.py | from collections import Counter | 3. Huffman Coding |
| deflate_combined.py | def deflate_compress(data): | 4. Combining LZ77 and Huffman in Deflate |
| deflate_considerations.py | def compress_with_zlib(data, level=6): | 5. Implementation Considerations |
| deflate_performance.py | data = b"A" * 1000000 # 1 MB of repeated 'A' | 6. Performance Optimization |
Key takeaways
Interview Questions on This Topic
Explain how LZ77 compression works and how it is used in Deflate.
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Drawn from code that ran under real load.
That's Data Compression. Mark it forged?
3 min read · try the examples if you haven't