Skip to content
Hello Python
Data Structure3 Practice19 Interview

Hash Map

Use Python dictionaries to turn repeated searches into direct key lookups, build frequency tables, group records, and preserve interview invariants.

Recognize it when

A brute-force inner loop repeatedly searches for a value, complement, identifier, or previously seen state.

Pybit arranges distinct geometric objects at a focused lookup console.
On this page · Mental Model

Checking your account…

Sign in to save your code and progress across devices. The lesson and problem statement remain public.

Hash Map Code Labs

Mental Model

A hash map stores an association from a key to a value. The key is how you ask a question. The value is the answer you want to retrieve later.

For an interview problem, the most important design decision is not the dictionary syntax. It is the meaning of the association:

Question you need to answer Useful association
Have I seen this number before, and where? number -> earlier_index
How often has this token appeared? token -> count
Which records share the same signature? signature -> list_of_records
What result did this subproblem produce? state -> computed_result

Write that relationship down before coding. If the key or value changes meaning halfway through the loop, the implementation becomes difficult to reason about and hidden edge cases follow.

Python’s dict is the general-purpose mapping type used for these associations. It preserves insertion order, but exact-key lookup is still the central capability. Verify the language contract in the Python documentation(opens in a new tab). Do not choose a dictionary only because you want the first, smallest, or next key. Those are different query requirements.

Python Dict Operations

Create a dictionary with a literal, a constructor, or a comprehension:

Construct dictionaries deliberately

Reference
scores = {"Ada": 92, "Grace": 98}
empty = dict()
squares = {number: number * number for number in range(5)}
Practice

Create scores for Ada and Grace, an empty dictionary, and a square-number mapping with a dictionary comprehension.

Public tests

  • Build the requested dictionaries

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Read, insert, update, test membership, and delete with operations that state your intent:

Read, update, and remove dictionary values

Reference
scores = {"Ada": 92, "Grace": 98}
scores["Linus"] = 88
scores["Ada"] = 95

ada_score = scores["Ada"]
optional = scores.get("Guido")
with_default = scores.get("Guido", 0)
grace_score = scores["Grace"] if "Grace" in scores else None

removed = scores.pop("Linus", None)
del scores["Ada"]
Practice

Insert and update names, read required and optional keys safely, then remove the requested entries without changing Grace's score.

Public tests

  • Preserve each requested missing-key behavior

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Iterating a dictionary produces keys. Use .items() when both parts of the association matter:

Iterate keys and key-value pairs

Reference
scores = {"Ada": 95, "Grace": 98}
names = []
pairs = []

for name in scores:
    names.append(name)

for name, score in scores.items():
    pairs.append((name, score))
Practice

Collect dictionary keys through normal iteration, then collect matching name and score pairs through items().

Public tests

  • Keep keys separate from key-value pairs

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Do not change dictionary size during a traversal of its live views. Iterate over a snapshot when deletion is intentional:

Delete from a safe key snapshot

Reference
scores = {"Ada": 92, "Grace": 98, "Linus": 87}

for name in list(scores):
    if scores[name] < 90:
        del scores[name]
Practice

Remove scores below 90 while preserving the qualifying entries by iterating a snapshot instead of a live dictionary view.

Public tests

  • Delete only low scores without a live-view mutation

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Choose the Missing-Key Behavior Deliberately

These tools solve related but different problems:

Tool Missing key behavior Best fit
mapping[key] Raises KeyError Absence violates the contract
mapping.get(key, default) Returns default without insertion Optional read
mapping.setdefault(key, default) Inserts and returns default Small accumulation task
defaultdict(factory) Bracket access creates a factory value Repeated grouping or accumulation
Counter(iterable) Missing counts read as zero Frequency and multiset work

setdefault is concise for a small grouping loop:

Group values with setdefault

Reference
words = ["tea", "eat", "tan"]
groups = {}

for word in words:
    signature = "".join(sorted(word))
    groups.setdefault(signature, []).append(word)

grouped = list(groups.values())
Practice

Group words by their sorted-character signature with setdefault so each signature receives a growing list.

Public tests

  • Put anagrams in the same list

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

For a larger grouping routine, defaultdict makes the accumulation rule explicit:

Group values with defaultdict

Reference
from collections import defaultdict

words = ["tea", "eat", "tan"]
groups = defaultdict(list)

for word in words:
    signature = "".join(sorted(word))
    groups[signature].append(word)

grouped = list(groups.values())
Practice

Use defaultdict(list) to make the repeated grouping rule explicit while keeping equivalent words under one signature.

Public tests

  • Create a list only when a signature is used

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Practical Code Patterns

Frequency Map

Write the manual form when the interview expects you to demonstrate the invariant:

Build a frequency map manually

Reference
def count_words(words):
    counts = {}
    for word in words:
        counts[word] = counts.get(word, 0) + 1
    return counts

counts = count_words(["red", "blue", "red", "green"])
Practice

Implement count_words with get so every word increments its own count without a separate membership branch.

Public tests

  • Count repeated and unique words

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Use Counter when the problem simply needs standard frequency behavior:

Use Counter for standard frequencies

Reference
from collections import Counter

counts = Counter(["red", "blue", "red"])
most_common = counts.most_common(1)
Practice

Build a Counter from the color sequence and ask it for the most common one-item frequency result.

Public tests

  • Report the highest frequency color

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

Group by a Canonical Key

The dictionary mechanics stay simple. The interview insight is choosing a stable signature that places equivalent inputs in the same group:

Group anagrams by a tuple key

Reference
from collections import defaultdict

def group_anagrams(words):
    groups = defaultdict(list)
    for word in words:
        signature = tuple(sorted(word))
        groups[signature].append(word)
    return list(groups.values())

groups = group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"])
Practice

Use a hashable sorted-character tuple as the key so each anagram group keeps the original words together.

Public tests

  • Use a stable, hashable signature for each group

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

The tuple is hashable. A list containing the same characters would not be a valid key.

One-Pass Complement Lookup

Find a complement in one pass

Reference
def two_sum(values, target):
    seen = {}  # value -> earlier index

    for index, value in enumerate(values):
        complement = target - value
        if complement in seen:
            return [seen[complement], index]
        seen[value] = index

    return []
Practice

Check the complement before storing the current value so the map contains only earlier indices and never reuses an index.

Public tests

  • Return two distinct original indices

Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor

The order is the proof. Before each lookup, seen contains only earlier indices. A match therefore uses two different positions, and the returned indices remain in original input order.

How Lookup Works

At the abstract data-structure level, a lookup follows three ideas:

  1. Python obtains hash and equality behavior from the key.
  2. The mapping uses that information to narrow the search for a matching key.
  3. If an equal key is present, the mapping returns the associated value.

Different keys can produce the same hash. A correct mapping still uses equality to distinguish them. This is why a hash match alone cannot prove that two keys are the same.

The key must keep stable hash and equality behavior while stored. Immutable built-ins such as strings, numbers, and tuples of hashable values are common keys. Mutable containers such as lists, dictionaries, and sets are not hashable.

This guide intentionally stops at the language-level contract. You do not need CPython table layout details to choose a dictionary, write a correct invariant, or explain average complexity in a coding round.

Interview Recognition Patterns

Look for the inner search in a brute-force solution. If every iteration scans earlier values to answer one of these questions, a dictionary may replace that scan:

  • Where did I see this value?
  • How many times has this value appeared?
  • Which group owns this item?
  • Have I already computed this state?
  • What earlier prefix would complete the current target?

Then define the association in one sentence and decide when it becomes valid. That timing often separates a correct solution from a plausible one.

For Two Sum(opens in a new tab), check before inserting. For prefix-sum counting(opens in a new tab), seed the empty prefix before the loop. For grouping(opens in a new tab), compute one canonical signature per item. For sliding-window counts(opens in a new tab), update both the entering and leaving sides so the map describes exactly the current window.

When the prompt asks only whether something exists, compare a set. When it requires a value, count, index, group, or computed result, a dictionary is usually the clearer model.

Python Version Note

Complexity & Invariants

Interview bounds depend on the stated representation, input model, and real Python operations.

OperationAverageWorstInterview note
Lookup, insert, update, or delete by keyO(1)O(n)Average behavior assumes ordinary hashing and collision behavior. Do not describe it as a worst-case guarantee.
Membership test by keyO(1)O(n)The expression key in mapping checks keys, not values.
Iterate keys, values, or itemsO(n)O(n)A complete traversal visits each of the n stored entries.

Space

O(n) for n stored key-value pairs, plus the storage used by keys and values themselves.

Assumptions

  • Keys have stable hash and equality behavior while stored.
  • Average bounds assume the runtime can keep collision behavior and table growth within normal operating conditions.
  • Hashing a key may not be O(1) when the key itself contains data that must be traversed, such as a long string or tuple.

Invariants Worth Saying Aloud

  1. Each key has one precise meaning, and every stored value matches that meaning throughout the algorithm.
  2. In one-pass complement problems, the map contains only information from earlier positions before the current lookup.
  3. Keys remain hashable and are not mutated in a way that changes equality or hash behavior while stored.

When To Use Or Avoid Hash Map

Use It When

  • You need to associate a stable, hashable key with a value and perform many lookups or updates.
  • You can trade O(n) extra space for an O(n) single pass instead of an O(n squared) nested search.
  • You need a frequency table, a grouping table, an index lookup, or memoized state.

Choose Another Tool When

  • You only need presence and no attached value, in which case a set communicates the intent more directly.
  • The input is already sorted and a two-pointer solution provides the same result with less auxiliary space.
  • The task requires ordered range queries, predecessor search, or smallest-key retrieval rather than exact-key lookup.

Common Pitfalls

These are the mistakes most likely to survive a happy-path example and fail a boundary case.

Treating a missing key as impossible

Bracket lookup raises KeyError when the key is absent, while get returns a fallback. Choosing the wrong operation can hide a bug or crash on a valid input.

Prevent it: Use brackets when absence violates the contract, and use membership or get when absence is an expected state.

Avoid

score = scores["Guido"]

Use instead

score = scores.get("Guido", 0)

Inserting a default while only trying to read

setdefault and defaultdict bracket access can create entries. That mutation may change length, output, or later control flow.

Prevent it: Use get for a non-mutating optional read, and reserve default-producing APIs for deliberate accumulation.

Avoid

if groups.setdefault(signature, []):
    process(groups[signature])

Use instead

members = groups.get(signature)
if members is not None:
    process(members)

Using a mutable container as a key

Lists, dictionaries, and sets are not valid dictionary keys because their mutable contents cannot provide stable hash behavior.

Prevent it: Convert structural keys to an immutable representation such as a tuple or frozenset when that representation matches the problem.

Avoid

positions[[row, column]] = value

Use instead

positions[(row, column)] = value

Changing dictionary size during iteration

Adding or deleting entries while iterating a dictionary view may raise RuntimeError or skip entries.

Prevent it: Iterate over list(mapping), build a new dictionary, or collect keys to remove and mutate after traversal.

Avoid

for key in scores:
    del scores[key]

Use instead

for key in list(scores):
    del scores[key]

Reusing the current element in a complement lookup

In Two Sum style problems, inserting the current value before checking its complement can match the current index with itself.

Prevent it: Check for the complement first, then insert the current value and index.

Avoid

seen[value] = index
if target - value in seen:
    return [seen[target - value], index]

Use instead

if target - value in seen:
    return [seen[target - value], index]
seen[value] = index

Reviewed References

Official Python documentation supports language behavior. Canonical problem pages provide additional practice context.