When does a trie use more memory than a hash set holding the same keys?
answer
- count nodes, not symbols
- nodes equal distinct prefixes
- what a node costs beyond its symbol
- random keys share almost nothing
- collapse the non-branching chains
basics
~20 sA trie saves memory only when keys share long prefixes. For long, mostly distinct keys it allocates about one node per symbol, each with child links and a marker — usually far more than storing the keys whole in a hash set.
solid answer
~50 sCount nodes, not symbols. An uncompressed trie has one node per *distinct prefix*, bounded above by the total length of all keys, and each node pays for a children container plus a terminal flag — tens of bytes for a structure that carries one symbol of information. A hash set pays one entry plus the key bytes plus table slack, per key. So sharing has to be large enough to repay the per-node overhead. Store ten thousand random 64-symbol content identifiers and sharing is exhausted after four or five symbols, leaving roughly six hundred thousand nodes against ten thousand entries — an order of magnitude the wrong way. The fix is compression: collapse each maximal non-branching chain into one edge labelled with the whole substring, which is a radix trie and cuts node count to O(number of keys).
go deeper
Know that a trie allocates a node per distinct prefix, not per key, and that each node costs more than the single symbol it represents. Be able to say when sharing helps and when it does not.
Explain both terms of the comparison out loud: distinct-prefix count times per-node overhead against entry count times key bytes plus table slack. Then name compression as the lever that attacks the first term.
Show you would measure before choosing: node count, fanout distribution and bytes per node on the real key distribution, plus the cache behaviour of chasing one pointer per symbol versus a single table probe.
Frame it as paying memory for a query capability. If only exact membership is required, defend the flat structure; if prefix enumeration or ranking is on the roadmap, justify the trie's footprint against that future requirement rather than today's lookups.
**Count nodes, not symbols** The claim "tries save space because prefixes are shared" is half of an accounting exercise, and it is the half that flatters tries. The right model has two terms. *Trie:* the node count of an uncompressed trie equals the number of **distinct prefixes** over the key set. Its upper bound is the sum of all key lengths (no sharing at all), its lower bound is roughly the length of the longest key (total sharing). Each node then costs whatever its children container costs, plus a terminal flag, plus allocator and pointer overhead — commonly tens of bytes to carry a single symbol's worth of information. *Hash set:* one entry per key, holding the key bytes (or a reference to them) and a hash or tag, in a table deliberately kept below full occupancy so probes stay short. Memory grows with the number of keys and the total key bytes, and there is no per-symbol structure at all. **Where sharing pays, and where it does not** Sharing is a property of the key *distribution*, not of tries. Take ten thousand randomly generated 64-symbol content identifiers drawn from a 16-symbol alphabet. There are 65,536 distinct four-symbol prefixes, far more than ten thousand keys, so after about four levels almost every key is already alone on its own path. Total nodes land near 10,000 x 60 = 600,000. The hash set holds 10,000 entries. Even if a trie node were as cheap as a hash entry — it is not, because it also stores child links — the trie loses by roughly sixty to one. Now take keys that are hierarchical: request paths, package-style identifiers, catalogue codes with a shared vendor prefix, natural-language word lists where thousands of entries share a stem. Here the distinct-prefix count is a small multiple of the key count, and the trie's per-node overhead is spread across many keys. This is the regime the folklore describes, and it is real — it is just not universal. | Key set | Distinct prefixes vs keys | Which is smaller | |---|---|---| | Random long identifiers | ~60x more nodes than keys | hash set, by a lot | | Hierarchical path-like keys | ~2-5x more nodes than keys | close; depends on node cost | | Short keys over a tiny alphabet | ~1-2x | trie can win outright | **The compression fix** Most of the waste in the bad case is long chains of single-child nodes — the unique tail of each key, one node per symbol, each carrying a container built to hold many children and holding one. A **radix (compressed) trie** collapses every maximal non-branching chain into a single edge labelled with the whole substring. Node count drops from O(total symbols) to O(number of keys): with k keys there are at most k leaves and at most k-1 branching internal nodes. In the identifier example that is roughly twenty thousand nodes instead of six hundred thousand. What compression does *not* change: lookup still reads all L symbols of the query, because the edge labels must be compared symbol by symbol. Compression removes nodes, not work. It also complicates insertion, which now has to *split* an edge when a new key diverges in the middle of a label. **Two more terms people forget** *Per-node fanout storage.* If each node reserves one slot per possible symbol, node cost scales with the alphabet, and almost all slots are empty — most nodes in a real trie have one or two children. Choosing a smaller children representation moves the total as much as compression does. *Locality.* A trie lookup chases L dependent pointers, each likely a cache miss, in an order the hardware cannot predict. A hash set does one hash and typically one or two probes into a contiguous table. Even at equal memory, the flat structure is usually faster for pure membership. **So why build one at all** Because the memory buys a capability, not speed. A hash set can answer only "is this exact key stored". A trie enumerates every key under a prefix, counts them, finds the longest stored prefix of a query, and yields keys in sorted order — none of which a hash structure can do without scanning everything. Judge the memory against the queries you need, not against membership alone. If membership is all you need, the trie is a worse hash set. **The one-line takeaway** A trie's size is driven by distinct prefixes and per-node overhead; sharing only pays when the keys genuinely overlap, and compression is what rescues the case where they do not.
- What does a radix (compressed) trie change about the node count?It collapses each maximal non-branching chain into one edge labelled with the whole substring, so node count falls from O(total symbols) to O(number of keys) — at most k leaves and k-1 branching nodes for k keys. Lookup still compares all L symbols of the query, so this is a space win, not a time win, and insertion gets harder because a new key can force an edge split.
- Does the trie's extra memory buy anything a hash set cannot do?Yes, and that is the only reason to pay it. A trie enumerates all keys under a prefix, counts them, returns keys in sorted order, and finds the longest stored prefix of a query. A hash structure answers exact membership only; every one of those queries would degrade to a full scan.
- How does key length change the comparison?A hash set grows with the key count and the total key bytes. An uncompressed trie grows with distinct prefixes, so a long unique suffix costs one node per symbol and returns nothing. Long keys with short shared heads are the worst case for a trie, and are exactly the case compression removes.
Shared prefixes are like roommates splitting rent: worth it only when enough of them share the same rooms. Give every tenant a private hallway to their own room and you end up paying for the hallways too.
saying these in an interview costs you the question
- Claims shared prefixes always make a trie smaller
- Counts one node per key instead of per prefix
- Ignores the child-link storage inside every node
- Says compression improves the asymptotic lookup cost
- Thinks a hash set can enumerate keys by prefix