skip to content

questions

8

When do you reach for a hash set instead of a hash map, and what does that choice signal?

level: juniorimportance: must knowfreq 84%

answer

  1. start from what you actually store
  2. membership only, or membership plus data
  3. one answers have I seen this
  4. the other binds a key to a payload
  5. the declared type documents the intent

basics

~20 s

Reach for a hash set when membership is the only fact you need, and a hash map when every key must carry data with it. A crawler tracking already-fetched addresses wants a set; a document indexer counting how often each word appears needs a map from word to count.

solid answer

~50 s

The deciding question is whether anything has to travel with the key. A hash set answers exactly one question — have I seen this before? — and stores nothing else, which is why a crawler's already-fetched-address structure is a set. A hash map binds each key to a payload, so a document indexer that reports how many times each word occurs needs a map from word to a running count. Both give expected O(1) insertion and lookup (O(n) worst case if hashing degrades), so the choice is not about speed; it is about what you store and what the declared type tells the next reader. Choosing a map and parking a meaningless placeholder in every value invites readers to hunt for significance that is not there, and pays for a value slot per entry. Choosing a set when you will later need the count forces a rewrite.

go deeper

for a junior

Be ready to state the rule in one breath: a set when membership is the only fact, a map when each key carries data. Have one concrete example of each ready before the interviewer asks for one.

for a middle

Explain why the two are not interchangeable: the value slot per entry, the fact that equal keys collapse in a set, and that lookup cost is driven by the key alone in both structures.

for a senior

Show the judgment side. Argue the choice as a maintenance decision — the declared type is documentation that prevents misuse — and be able to quantify roughly what a value slot costs across tens of millions of entries.

for a principal

Own it as a code-health convention: default to the narrowest structure that satisfies the requirement, and treat placeholder-valued maps as a review finding, because every unexplained degree of freedom becomes someone's future bug.

## Same machinery, two different questions A hash set and a hash map are built from the same parts: a hash function turns a key into a bucket index, entries land in buckets, and collisions are resolved by chaining or probing. Insertion, lookup and deletion are **expected** O(1) — amortized over resizes — and O(n) in the worst case when many keys collide. Because the cost model is essentially the same for both, speed is almost never the reason to prefer one over the other. What differs is *what each entry holds* and *what question the structure is built to answer*. - A **hash set** stores keys only. Its interface answers `contains(key)` and nothing more. - A **hash map** stores a key and an associated value. Its interface answers `get(key)`, which returns data you put there. So the selection rule is short: **does anything need to travel with the key?** If no, a set. If yes, a map. ## Two worked cases **A crawler's already-fetched addresses.** The crawler pulls a page address off a queue and needs to know one thing: has this address been fetched already? There is no second fact to record — no count, no timestamp anyone reads, no payload. A hash set of addresses is the exact shape of the requirement. Membership goes in, membership comes out. **A document indexer's word frequencies.** The indexer walks the tokens of a corpus and must report, at the end, how many times each distinct word occurred. Presence is not enough; the number is the product. That demands a map from word to a running count, incremented on each occurrence. A set here is not merely inconvenient, it is *wrong*: a set ignores repeated insertions of an equal key, so after processing a million tokens the set knows only the vocabulary, not the frequencies. A very common wrong answer is "insert the word once per occurrence and the set will hold them all" — it will not; equal keys collapse into one entry, which is the entire point of a set. ## Why the choice is not cosmetic Three things actually differ. **Storage per entry.** A map entry carries a value slot in addition to the key; a set entry does not. On a structure holding tens of millions of entries that slot is real memory, even when every value is the same meaningless placeholder. It is not usually the dominant term — the keys themselves, per-entry bookkeeping and the empty slack a hash table keeps to stay below its load factor typically weigh more — but it is not zero either. **Correctness of intent.** A map whose values are all a constant placeholder is a set wearing a disguise. Later, someone reads `get(key)` in the code and reasonably assumes the value means something. Someone else "helpfully" starts writing real values into it, and now two call sites disagree about what the structure is for. Declaring a set makes the misuse unrepresentable. **What the type documents.** The type is the cheapest comment in the codebase. A reviewer who sees a set knows immediately that only membership matters and can stop asking what the value is. This is why "use a map, it is more flexible" is a poor default: flexibility you do not need is an open question you leave for the next reader. ## The direction of the cost claims Be precise when you say this out loud. Neither structure is O(1) unconditionally: the bound is expected and amortized, and it rests on a hash function that spreads *your* keys well and on resizing to keep the load factor down. A single insertion can be expensive when it triggers a resize that rehashes everything. And a map is not slower than a set at lookup: the lookup path hashes and compares the **key**; the value is read only after a match is found, and it never participates in choosing a bucket. One genuine degree of freedom worth knowing: iteration order is not part of the abstraction, and mainstream runtimes made different calls on it — JavaScript's built-in keyed collections iterate in insertion order by specification, while Python's plain hash-based mapping gained insertion-order iteration only through a later compact redesign, and plenty of other standard libraries promise no order at all (some deliberately randomize it). Never write logic that depends on the traversal order of a hash-based container unless the container's contract explicitly promises one. ## How to answer in an interview Say the rule (does data travel with the key?), give one example of each shape, then name the two consequences — a value slot per entry and the intent the type communicates — and close by noting both structures share the same expected O(1)/worst-case O(n) profile, so the decision is about meaning and memory, not speed.

  • A colleague uses a hash map with a constant placeholder in every value slot instead of a hash set. What do you say in review?
    That it is a set with extra ceremony. Every entry pays for a value slot that carries no information, and every future reader has to work out that the value is meaningless — or worse, starts writing real values into it and creates two contradictory readings of the same structure. Declaring a set removes the ambiguity and the slot. The only fair defence is that the surrounding code truly needs a map interface for other reasons.
  • Does adding values to the entries make lookups in a hash map asymptotically slower than in a hash set?
    No. Lookup hashes the key and compares candidate keys in the bucket; the value is fetched only after a match and never influences the bucket choice or the comparisons. Both are expected O(1) and worst-case O(n) under heavy collisions. The value costs memory per entry — and, indirectly, cache locality on large structures — not asymptotic time.
  • You built a set of distinct order identifiers and now the requirement adds "report when each was first seen". What changes?
    The requirement adds data that must travel with the key, so the structure becomes a map from identifier to first-seen timestamp. The insertion logic changes too: with a set you insert unconditionally, but with a first-seen map you must write only when the key is absent, otherwise later occurrences overwrite the earliest timestamp you were asked to keep.

A guest list at the door answers only "are you on it?"; a ledger answers "how many times has this guest come in?". You cannot get the second answer from the first, no matter how many times you tick the name.

saying these in an interview costs you the question

  • Says a set and a map cost the same, so the choice is purely cosmetic
  • Uses a map with placeholder values everywhere and calls it equivalent
  • Claims hash lookups are O(1) with no expected-versus-worst-case caveat
  • Thinks a set can count occurrences if you insert duplicates
  • Picks a map by default and only later asks what the value means

context

open as a page

Why does storing each scanned value's index in a hash map find a matching pair in one pass?

level: middleimportance: must knowfreq 76%

basics

~20 s

Because for each record you can compute the partner you need and ask the map whether it has already been seen, in expected constant time. That replaces the inner loop of a nested scan, turning O(n^2) comparisons into one O(n) pass that trades O(n) memory for the speedup.

open as a page

Why do production hash tables pick power-of-two capacities with bit masking over prime-modulo sizing?

level: middleimportance: must knowfreq 62%

basics

~20 s

Masking with a power-of-two capacity turns the index step into a single bitwise AND instead of a division, and doubling splits each bucket cleanly. Prime sizing exists to blend weak hash bits; masked tables buy that with an explicit mixing step.

open as a page

How is a hash set usually implemented on top of a hash map engine, and what does an entry store?

level: juniorimportance: should knowfreq 58%

basics

~20 s

A hash set is normally the same hash table with the value half unused: each entry holds the key and its cached hash, and a membership test is an ordinary lookup reporting found or not found.

open as a page

Why does a one-pass seen-set beat sorting for finding the first repeated check-in badge?

level: middleimportance: should knowfreq 58%

basics

~20 s

Sorting answers a different question: it destroys arrival order, so "first repeat" stops being defined, and it costs O(n log n). A one-pass seen-set tests membership before each insert, reports the earliest repeated badge in O(n) expected time, and stops the moment it finds one.

open as a page

Why do hash tables that index by bit masking XOR a hash's high bits downward?

level: middleimportance: should knowfreq 46%

basics

~20 s

A power-of-two mask reads only the low bits, so hashes differing solely up high would collide every time. XOR-ing the high half down folds that entropy into the bits the mask reads, for one shift and one XOR.

open as a page

A teammate swaps a dedupe seen-set for a map from key to the full record. What does that cost?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Memory and lifetime, not speed. Lookups still hash and compare the key alone, so they stay expected O(1); but the structure's footprint now scales with record size rather than key size, and every record it holds stays reachable — un-reclaimable — for the whole run.

open as a page

Why does a compact open-addressed hash table usually out-run node-per-entry chaining on lookups?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Open addressing keeps entries in one contiguous array, so a probe touches neighbouring memory and a small metadata byte per slot rejects mismatches before any key is read. Chaining follows a pointer to a separately allocated node for every candidate.

open as a page