Python 3.7+
Dictionary insertion order is a language guarantee. Updating a key keeps its position, while deleting and reinserting it places it at the end.
Verify in Python docs(opens in a new tab)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.

Sign in to save your code and progress across devices. The lesson and problem statement remain public.
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.
Create a dictionary with a literal, a constructor, or a comprehension:
scores = {"Ada": 92, "Grace": 98}
empty = dict()
squares = {number: number * number for number in range(5)}Create scores for Ada and Grace, an empty dictionary, and a square-number mapping with a dictionary comprehension.
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:
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"]Insert and update names, read required and optional keys safely, then remove the requested entries without changing Grace's score.
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:
scores = {"Ada": 95, "Grace": 98}
names = []
pairs = []
for name in scores:
names.append(name)
for name, score in scores.items():
pairs.append((name, score))Collect dictionary keys through normal iteration, then collect matching name and score pairs through items().
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:
scores = {"Ada": 92, "Grace": 98, "Linus": 87}
for name in list(scores):
if scores[name] < 90:
del scores[name]Remove scores below 90 while preserving the qualifying entries by iterating a snapshot instead of a live dictionary view.
Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor
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:
words = ["tea", "eat", "tan"]
groups = {}
for word in words:
signature = "".join(sorted(word))
groups.setdefault(signature, []).append(word)
grouped = list(groups.values())Group words by their sorted-character signature with setdefault so each signature receives a growing 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:
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())Use defaultdict(list) to make the repeated grouping rule explicit while keeping equivalent words under one signature.
Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor
Write the manual form when the interview expects you to demonstrate the invariant:
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"])Implement count_words with get so every word increments its own count without a separate membership branch.
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:
from collections import Counter
counts = Counter(["red", "blue", "red"])
most_common = counts.most_common(1)Build a Counter from the color sequence and ask it for the most common one-item frequency result.
Loading interactive editor.If it does not appear, the reference and starter code remain readable.Reload editor
The dictionary mechanics stay simple. The interview insight is choosing a stable signature that places equivalent inputs in the same group:
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"])Use a hashable sorted-character tuple as the key so each anagram group keeps the original words together.
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.
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 []Check the complement before storing the current value so the map contains only earlier indices and never reuses an index.
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.
At the abstract data-structure level, a lookup follows three ideas:
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.
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:
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 3.7+
Dictionary insertion order is a language guarantee. Updating a key keeps its position, while deleting and reinserting it places it at the end.
Verify in Python docs(opens in a new tab)Interview bounds depend on the stated representation, input model, and real Python operations.
| Operation | Average | Worst | Interview note |
|---|---|---|---|
| Lookup, insert, update, or delete by key | O(1) | O(n) | Average behavior assumes ordinary hashing and collision behavior. Do not describe it as a worst-case guarantee. |
| Membership test by key | O(1) | O(n) | The expression key in mapping checks keys, not values. |
| Iterate keys, values, or items | O(n) | O(n) | A complete traversal visits each of the n stored entries. |
O(n) for n stored key-value pairs, plus the storage used by keys and values themselves.
These are the mistakes most likely to survive a happy-path example and fail a boundary case.
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)Where you will hit this: Two Sum(opens in a new tab)
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)Where you will hit this: Group Words by Anagram Signature(opens in a new tab)
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]] = valueUse instead
positions[(row, column)] = valueWhere you will hit this: Group Words by Anagram Signature(opens in a new tab)
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]Where you will hit this: Design HashMap(opens in a new tab)
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] = indexWhere you will hit this: Two Sum(opens in a new tab)
Official Python documentation supports language behavior. Canonical problem pages provide additional practice context.
python-docs · checked 2026-07-27
python-docs · checked 2026-08-02
python-docs · checked 2026-08-02
leetcode · checked 2026-07-12
leetcode · checked 2026-07-12
leetcode · checked 2026-07-12
leetcode · checked 2026-07-12