Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Build an optimal prefix code by repeatedly merging the two LEAST frequent symbols into a parent whose frequency is their sum, using a min-heap. Each merge creates an internal tree node; after n−1 merges one root remains. A symbol's code length equals its depth in this tree, so rare symbols sit deep (long codes) and frequent symbols sit shallow (short codes). The total encoded bits = Σ frequency × depth, and this greedy provably minimises it. O(n log n) with a min-heap.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you build the tree bottom-up by always merging the two lowest-frequency nodes with a min-heap, understand that code length equals tree depth, and can argue why greedy yields a minimum-total-length prefix code.
Where candidates slip
Merging the two largest, forgetting to push the merged node back so it can be re-merged, or assigning codes to internal nodes instead of only leaves (breaking the prefix property).
How to stand out
State the exchange argument — the two least frequent symbols can be assumed to be the deepest siblings in some optimal tree, so merging them first is safe — and connect it to the optimal-binary-merge-tree family (connect ropes, file merging).
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMinimise total bits of a prefix codeFrequencies fixed and known?No, streamingYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Repeated Two-Smallest Merge (Baseline)
1Hint 1Explore hintHide hint
A symbol's code length is its depth in the tree, so total bits = Σ frequency × depth. To shrink the total, which symbols should sit deepest (longest codes)?
2Hint 2Explore hintHide hint
The rarest ones. So build the tree bottom-up: repeatedly take the two lowest-frequency nodes and join them under a new parent whose frequency is their sum.
3Hint 3Explore hintHide hint
Without a heap: each round scan all live nodes for the two smallest frequencies, merge them, and add the new parent back. Repeat until one node remains; the answer is the sum of all merge frequencies.
A · Core idea
Treat each symbol as a leaf node carrying its frequency. Each round, find the two nodes with the lowest frequencies, create a parent whose frequency is their sum (with those two as children), and add the parent back to the pool. After n − 1 merges a single root remains; the minimum total encoded length is the sum of all the parent frequencies created along the way.
Why total bits equals Σ frequency × depth. Each left/right edge from the root adds one bit to a codeword, so a leaf at depth d has a d-bit code and contributes frequency × d bits. Equivalently, every time a node is buried under a new parent its frequency is counted one more time — which is exactly why summing the parent frequencies over all merges equals Σ frequency × depth. That dual view (sum of merge costs = weighted depth) is the same identity as connecting ropes.
Why merge the two rarest. You want long codes (deep leaves) for symbols that appear rarely, since their length is multiplied by a small frequency. Merging the two lowest-frequency nodes pushes them one level deeper and creates a combined node that, being small, will likely be merged again soon — keeping rare symbols sinking while frequent symbols stay near the root. That's the greedy that minimises the weighted depth.
Why prefix-correctness is automatic. Because symbols only ever become leaves (we merge nodes into a new parent, never attach a symbol mid-tree), no codeword is a prefix of another. The tree structure itself enforces the prefix property.
Why we don't ship this. Re-scanning all live nodes for the two minimums every round is O(n²). We only ever need the two smallest repeatedly, with one insertion after each merge — the textbook case for a min-heap, which the optimal approach uses.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
freq = [4, 3, 2, 6]round 1
two smallest 2,3 → parent 5
pool=[4,6,5], total=529C · Code with teaching comments
Reference implementation previewpublic long huffmanCost(int[] freq) { if (freq.length == 1) { return freq[0]; // single symbol: 1-bit code by convention } List<Long> pool = new ArrayList<>();Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The algorithm is one greedy rule — merge the two rarest — justified by 'code length = depth'. The O(n²) is purely the repeated scan for the two minimums; a min-heap finds them in O(log n) and is the only change needed.
ComplexityO(n²) time, O(n) space — Each round scans up to n live nodes for the two smallest → O(n) per round, with n−1 merges → O(n²). Storing nodes costs O(n) space.
E · Edge cases and pitfalls5single symbol — Handled separately: 1-bit code, cost = frequency.Explore edge casesHide edge cases
Edge cases handled
single symbolHandled separately: 1-bit code, cost = frequency.two symbolsOne merge, both at depth 1 → total = freq[0] + freq[1].
Pitfalls
- Merging the two LARGEST — gives rare symbols short codes, maximising total bits.
- Assigning codes to internal nodes — breaks the prefix property.
- Skipping this approach — verbalising 'merge the two rarest, code length = depth' is what the interviewer grades.
Approach 2 of 2 · Optimal
Min-Heap Tree Construction (Optimal)
1Hint 1Explore hintHide hint
You never need all nodes sorted — only the two smallest, repeatedly, with one push after each merge. Which structure gives min-removal and insertion in O(log n)?
2Hint 2Explore hintHide hint
Build a min-heap of nodes keyed by frequency. Pop the two smallest, make a parent with their summed frequency, push the parent back.
3Hint 3Explore hintHide hint
Repeat until one node (the root) remains. Accumulate each parent's frequency for the total bits; to emit codes, walk the tree assigning 0 going left and 1 going right.
A · Core idea
Put every symbol into a min-heap keyed by frequency. While more than one node remains, pop the two smallest, create a parent whose frequency is their sum (children = the two popped nodes), and push the parent back. The last remaining node is the root. The minimum total encoded length is the sum of all parent frequencies; the per-symbol codes come from a traversal that appends 0 on each left edge and 1 on each right edge.
Why a min-heap is the right structure. The baseline only ever needs: remove the smallest, remove the next smallest, insert the merged parent. A min-heap does each in O(log n), so the whole construction is O(n log n) — versus the baseline's O(n²) re-scan.
The exchange argument that proves optimality. Let the two least-frequent symbols be x and y. Claim: some optimal tree has x and y as sibling leaves at maximum depth. Take any optimal tree and let a and b be two deepest sibling leaves. Since x and y are the least frequent, swapping x with a and y with b never increases Σ freq × depth (moving a smaller frequency deeper and a larger one shallower can only help or tie). So there's an optimal tree where x and y are deepest siblings. Replacing that pair with a single merged leaf of frequency freq(x)+freq(y) reduces the problem to n − 1 symbols, and the cost differs by exactly freq(x)+freq(y) — the merge cost we add. Inductively, the greedy that merges the two smallest each step is optimal.
Why summing parent frequencies gives the answer. Each merge's parent frequency is counted again in every later merge it participates in — that compounding is precisely Σ freq × depth. Accumulating a.freq + b.freq on every merge captures the total encoded length without ever computing depths explicitly.
Why decoding is unambiguous. Symbols are only ever leaves, so no codeword prefixes another. A decoder walks the tree bit by bit from the root and emits a symbol exactly when it reaches a leaf.
Practical notes. Keep frequencies and the total in 64-bit (long in Java; Python ints are unbounded). Decide a deterministic tie-break (e.g. insertion order) if you need reproducible codes, since equal frequencies can yield different but equally optimal trees.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
freq = {a:5, b:9, c:12, d:13, e:16, f:45}Interactive diagram
Huffman tree for the classic 6-symbol example
merge 5,9Leaves are symbols; internal nodes hold summed frequencies. Depth = code length, so f (45) is shallow, a (5) and b (9) are deep. Total bits = 224.
224C · Code with teaching comments
D · Why it works and complexityO(n log n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
This is the same optimal-binary-merge-tree engine as connecting ropes, but the tree is the deliverable: depths become code lengths. Whenever a baseline repeatedly needs 'the two smallest, then reinsert', a min-heap is the upgrade, and the exchange argument is the standard way to prove the greedy optimal.
ComplexityO(n log n) time, O(n) space — Heapify is O(n). There are n−1 merges; each does two pops and one push, all O(log n) → O(n log n). The heap and tree hold O(n) nodes → O(n) space.
E · Edge cases and pitfalls6single symbol — Returned early with a 1-bit code; no merges happen.Explore edge casesHide edge cases
Edge cases handled
single symbolReturned early with a 1-bit code; no merges happen.all equal frequencies (power of 2 count)Builds a balanced tree; every code has the same length — Huffman ties fixed-length coding.one dominant symbolThe frequent symbol stays near the root (short code); rare symbols sink deep — maximum compression benefit.
Pitfalls
- Comparing node objects directly in Python's heapq without a tie-breaker — throws when frequencies tie.
- Using a max-heap or merging the two largest — produces the worst, not best, total length.
- Forgetting the n = 1 case — a lone symbol still needs a 1-bit code, not zero.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic long huffmanCost(int[] freq) { if (freq.length == 1) { return freq[0]; } PriorityQueue<Node> heap = new PriorityQueue<>((x, y) -> Long.compare(x.freq, y.freq));Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Merging the two largest
Code length is depth, and you want rare symbols deep. Merging the largest first gives frequent symbols long codes and rare ones short codes — the maximum total length, exact opposite of the goal.
Explore the code comparisonHide the code comparison
Merging the two largest
Code length is depth, and you want rare symbols deep. Merging the largest first gives frequent symbols long codes and rare ones short codes — the maximum total length, exact opposite of the goal.
Don't write this
PriorityQueue<Node> heap = new PriorityQueue<>((x, y) -> Long.compare(y.freq, x.freq)); // max-heapWrite this instead
PriorityQueue<Node> heap = new PriorityQueue<>((x, y) -> Long.compare(x.freq, y.freq)); // min-heap2Uncomparable nodes in Python heapq
Pushing (freq, node) tuples makes heapq compare node objects when two freqs tie, raising a TypeError. Add a unique increasing counter as a tie-breaker so comparison never reaches the payload.
Explore the code comparisonHide the code comparison
Uncomparable nodes in Python heapq
Pushing (freq, node) tuples makes heapq compare node objects when two freqs tie, raising a TypeError. Add a unique increasing counter as a tie-breaker so comparison never reaches the payload.
Don't write this
heapq.heappush(heap, (merged_freq, (a, b))) # compares tuples → may compare a,bWrite this instead
heapq.heappush(heap, [merged_freq, counter, (a, b)])
counter += 1332-bit overflow on total bits
With 10⁵ symbols at 10⁹ frequency and non-trivial depths, Σ freq × depth exceeds 2³¹ and an int total wraps negative.
Explore the code comparisonHide the code comparison
32-bit overflow on total bits
With 10⁵ symbols at 10⁹ frequency and non-trivial depths, Σ freq × depth exceeds 2³¹ and an int total wraps negative.
Don't write this
int total = 0;
// ...
total += parent.freq;Write this instead
long total = 0;
// ...
total += parent.freq;Revise
Carry the pattern into the interview
Pattern to remember
Min-heap: merge the two lowest-frequency nodes until one tree remains
Code pattern to memorize
heap = min-heap of leaf nodes (by frequency)
while heap size > 1:
a = pop min
b = pop min
parent = node(freq = a.freq + b.freq, left = a, right = b)
push parent
root = pop
# code length of a symbol = its depth; assign 0/1 down each edgeRules to remember
- 1Always merge the two smallest-frequency nodes — the same two-smallest greedy as connecting ropes.
- 2The merged node's frequency is the sum of its children; push it back so it competes in future merges.
- 3It's a prefix code: every symbol is a leaf, so no code is a prefix of another and decoding is unambiguous.
- 4A symbol's code length = its depth in the tree, so total bits = Σ freq × depth — exactly the merge-cost sum.
- 5Multiple optimal trees can exist (ties break differently), but the total encoded length is unique.
Use this pattern when
- Build a variable-length prefix code that minimises total encoded length
- Compress data where symbol frequencies are known and skewed
- Any 'merge two cheapest, cost = sum, minimise total' optimal-binary-merge-tree problem
- You need an unambiguous code where no codeword prefixes another
Choose another pattern when
- All symbols equally frequent → fixed-length codes are already optimal, Huffman gives no gain
- You need codes of bounded maximum length → length-limited Huffman (package-merge), not plain greedy
- Frequencies arrive as a stream / change over time → adaptive Huffman or arithmetic coding
- You want the best possible ratio, not just prefix-optimal → arithmetic / range coding beats Huffman
Interview takeaway
Huffman is the canonical optimal prefix code: merge the two rarest symbols repeatedly so rare symbols end up deep (long codes) and common ones shallow (short codes). It's the same min-heap merge as 'connect ropes', and the exchange argument — the two lowest-frequency symbols can always be made deepest siblings — is what proves it optimal.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical optimal prefix code via the optimal-binary-merge-tree greedy. Recognise it whenever you minimise Σ frequency × depth (or any 'merge two cheapest, cost = sum, minimise total'): a min-heap merging the two smallest builds the tree in O(n log n). Same engine as connecting ropes; the exchange argument proves the two rarest can be deepest siblings.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Connect N Ropes with Minimum CostThe same min-heap merge — ropes are symbol frequencies, the answer is the merge-cost sum.
- Minimum Cost to Connect SticksComing soonIdentical optimal-binary-merge-tree problem stated with sticks.
- Length-Limited Huffman (Package-Merge)Coming soonBound the maximum code length — plain greedy no longer works; use the package-merge algorithm.
- Arithmetic / Adaptive CodingComing soonWhen frequencies stream or you want better-than-prefix ratios, move beyond static Huffman.