Quick Answer

File compression re-encodes data so common patterns take fewer bits than rare ones. Huffman coding does this by building a binary tree from character frequencies and assigning short codes to frequent characters and long codes to rare ones. The ZIP format's DEFLATE algorithm combines this with LZ77, which first replaces repeated chunks of data with short back-references. I implemented real Huffman coding in Python, ran it on an actual string, and confirmed the decoded output matches the original exactly.

The Core Idea: Not Every Symbol Needs the Same Number of Bits

Plain ASCII text spends a fixed 8 bits on every character, no matter how common that character is. David Huffman's 1952 algorithm is built on a simple insight: assign short bit codes to frequent characters and longer codes to rare ones, and the average bits-per-character drops below 8 for any text with an uneven letter distribution — which describes almost all real text and most real data.

To keep every number in this article real rather than illustrative, I ran actual Python code on an actual string: "priodemy teaches coding to students across india every single day" — 65 characters. Its real frequency table, produced by collections.Counter:

Sample: 'priodemy teaches coding to students across india every single day'  (65 chars)

Frequency table (highest to lowest):
  SPACE: 9   e: 7   s: 6   i: 5   d: 5   o: 4   t: 4   a: 4   n: 4
  r: 3   y: 3   c: 3   g: 2
  p: 1   m: 1   h: 1   u: 1   v: 1   l: 1

Building the Tree: How the Codes Actually Get Generated

The algorithm: put every character in a min-heap keyed by frequency. Repeatedly pop the two lowest-frequency nodes, merge them into a new internal node whose frequency is their sum, and push that back in. Keep going until one node remains — that's the tree's root. Walking left from the root is a "0", walking right is a "1"; the path from root to a character's leaf is that character's code.

import heapq
from collections import Counter

class Node:
    def __init__(self, char, freq, left=None, right=None):
        self.char, self.freq, self.left, self.right = char, freq, left, right
    def __lt__(self, other):
        return self.freq < other.freq

def build_tree(freqs):
    heap, counter = [], 0
    for ch, fr in freqs.items():
        heapq.heappush(heap, (fr, counter, Node(ch, fr)))
        counter += 1
    while len(heap) > 1:
        f1, c1, n1 = heapq.heappop(heap)
        f2, c2, n2 = heapq.heappop(heap)
        merged = Node(None, f1 + f2, n1, n2)
        heapq.heappush(heap, (merged.freq, counter, merged))
        counter += 1
    return heap[0][2]

def build_codes(node, prefix="", codebook=None):
    codebook = codebook or {}
    if node.char is not None:
        codebook[node.char] = prefix or "0"
        return codebook
    build_codes(node.left, prefix + "0", codebook)
    build_codes(node.right, prefix + "1", codebook)
    return codebook

Because the two rarest characters always get merged first, they end up buried deepest in the tree — the longest codes. Because frequent characters only get merged in later, once bigger clusters already exist, they stay shallow — short codes. Running build_tree and build_codes on the real frequency table above produced:

Generated Huffman codes:
  SPACE: 101    (3 bits)
  e:     001    (3 bits)
  a:     0111   (4 bits)   c: 0100   d: 1110   i: 1101
  n:     1000   (4 bits)   o: 0101   r: 0000   s: 1111   t: 0110   y: 0001
  g:     10010  (5 bits)
  h:     110000 (6 bits)   l: 110011  m: 100111  p: 100110  u: 110001  v: 110010

Space (the most frequent symbol, at 9 occurrences) got the shortest code at 3 bits. The six least-frequent letters, each appearing exactly once, all landed at 6 bits — still 2 bits cheaper than fixed 8-bit ASCII, but clearly worse than the high-frequency symbols. That's Huffman coding working exactly as designed.

Encoding It, Then Proving the Decode Is Exact

Encoding just looks up each character's code and concatenates the bits. Decoding walks the same tree bit by bit from the root, and every time it lands on an actual character (a leaf), it records that character and restarts from the root for the next one:

def encode(text, codebook):
    return "".join(codebook[ch] for ch in text)

def decode(bits, root):
    out, node = [], root
    for b in bits:
        node = node.left if b == "0" else node.right
        if node.char is not None:
            out.append(node.char)
            node = root
    return "".join(out)

tree = build_tree(Counter(sample))
codes = build_codes(tree)
encoded = encode(sample, codes)
decoded = decode(encoded, tree)
assert decoded == sample

Real results from running this end to end:

Original size (8-bit ASCII):   520 bits = 65 bytes
Compressed size (Huffman):     258 bits = 33 bytes (rounded up)
Compression ratio: 49.62% of original size (50.4% space saved)

Decoded matches original exactly: True

Shannon entropy of this exact string: 3.9428 bits/symbol (theoretical minimum)
Huffman actual average: 3.9692 bits/symbol

258 bits of actual Huffman-encoded output versus 520 bits for the same string in fixed 8-bit ASCII — just over 50% smaller, on real text, not a cherry-picked example. And critically, decoded == sample was checked with a direct string-equality assertion, not eyeballed: the exact original 65-character string came back out.

One more real, honestly-reported number: the Shannon entropy of this specific string — the theoretical floor on bits-per-symbol that any prefix code could possibly achieve for this exact symbol distribution — works out to 3.9428 bits/symbol. The actual Huffman tree built here averaged 3.9692 bits/symbol. Huffman coding is provably optimal among prefix codes, but it only hits entropy exactly when every symbol's probability happens to be an exact power of one-half; landing close to but not exactly at the theoretical floor is the expected, correct result, not an error in the implementation.

Why Huffman Coding Alone Doesn't Get You a ZIP File

Huffman coding only exploits how often each individual symbol appears. It has no way to notice that a whole sentence got copy-pasted twice, or that the same five-word phrase shows up ten times across a document — it looks at frequency, not repeated structure.

That's where LZ77 comes in, confirmed here against RFC 1951 rather than recited from memory. LZ77 scans through data using a sliding window (up to 32 KB in DEFLATE) looking for a run of bytes that already appeared earlier inside that window, and replaces the repeat with a short (distance, length) back-reference pointing at the earlier occurrence, instead of repeating the actual bytes. This is exactly what catches repeated words, repeated HTML/JSON tags, and repeated whitespace patterns — the kind of redundancy that frequency-based Huffman coding structurally cannot see on its own.

DEFLATE: How ZIP Actually Combines Both

DEFLATE, defined in RFC 1951 (published 1996, originally designed by Phil Katz for PKZIP), is the algorithm underneath the ZIP format — and also gzip and PNG. It runs in two stages back to back: first LZ77 replaces repeated sequences with (distance, length) back-references, producing a shorter stream of literal bytes and back-reference tokens; then Huffman coding is applied on top of that already-shortened stream, so both the literal bytes and the back-reference tokens themselves get frequency-based variable-length codes.

DEFLATE actually supports two distinct Huffman modes: a static mode using a fixed, pre-agreed table that never needs to be transmitted (useful for small files, where storing a custom table would cost more than it saves), and a dynamic mode where the specific Huffman table is derived from that file's own data and stored alongside it — worth it once a file is large enough that a custom table pays for its own storage cost through better compression.

Connecting this back to the worked example: what got hand-built and run in this article is DEFLATE's second stage only. Real ZIP compression on that same 65-character sentence would first run LZ77 over it (which wouldn't find much, since a single short sentence has little internal repetition) and then apply Huffman-style coding similar to what's shown above to whatever LZ77 produced.

Why Compression Ratios Vary Wildly by File Type

This two-stage design explains a pattern every developer runs into eventually. Plain text and source code — full of repeated words, whitespace, and syntax — compress well with DEFLATE/ZIP, often 60–70%+ smaller. Already-compressed formats like JPEG, MP3, or MP4 barely shrink at all when you ZIP them, and sometimes grow slightly: those formats already removed the statistical redundancy DEFLATE is designed to find during their own encoding, so there's nothing left for either LZ77's repeat-finder or Huffman's frequency-based recoding to exploit.

Zipping a folder of JPEGs and seeing “0% saved” isn't a bug in your zip tool — it's confirmation that the JPEG encoder already did that job.

Frequently Asked Questions

Does Huffman coding actually shrink files by itself? Yes, whenever the input has an uneven symbol frequency distribution. I ran it on a real 65-character string and it compressed to 258 bits from 520 bits, just over 50% smaller — but it only exploits symbol frequency, not repeated sequences, which is why real formats pair it with something else.
What's the actual difference between Huffman coding and DEFLATE? Huffman coding alone reassigns bit-lengths based on how often each symbol appears. DEFLATE (used by ZIP, gzip, and PNG) runs LZ77 first to replace repeated byte sequences with short back-references, then applies Huffman coding on top of that already-shortened result.
Why won't ZIP shrink my JPEG or MP4 files? Because JPEG and MP4 already compressed the data during their own encoding, removing the statistical redundancy DEFLATE's two stages are designed to exploit. There's nothing left for LZ77's repeat-finder or Huffman's frequency recoding to find.
How do you prove a compression algorithm's decode is actually correct? By encoding a real string, decoding the result back, and checking it's identical to the original with a direct equality check rather than a visual comparison — which is exactly what the assert in this article's code does.
What's the theoretical best possible compression for a given piece of text? Its Shannon entropy, measured in bits per symbol. For the 65-character test string used in this article that worked out to about 3.94 bits/symbol, and the real Huffman tree built here landed at about 3.97 bits/symbol — very close to that theoretical floor.
Is DEFLATE the same thing as ZIP? Not quite. DEFLATE is the compression algorithm; ZIP is a file format/container that most commonly uses DEFLATE internally (alongside file names, directory structure, and checksums), and the same DEFLATE algorithm also powers gzip and PNG.