🐍 Python Course

Dictionaries

4. Dictionaries

A dictionary is a mapping from keys to values.

Quick Reference: Dictionary methods

MethodPurposeReturnsComplexityNotes
get(key, default=None)Safely read valueValue or defaultO(1) averageNo KeyError if missing
keys()View of all keysdict_keys viewO(1)Dynamic view, not a list
values()View of all valuesdict_values viewO(1)Dynamic view
items()View of key-value pairsdict_items viewO(1)Very common for iteration
pop(key[, default])Remove and return value for keyValueO(1) averageRaises KeyError if missing and no default
popitem()Remove and return last inserted pair(key, value) tupleO(1) averageLIFO in modern Python
update(other)Merge in values from another mapping / iterableNoneO(k)Overwrites existing keys
setdefault(key[, default])Get value, insert default if missingValueO(1) averageUseful for grouping/counting
clear()Remove all itemsNoneO(n)Keeps same dict object
copy()Shallow copyNew dictO(n)Nested mutables are still shared
fromkeys(iterable, value=None)Create new dict with given keysNew dictO(n)All keys share same value object

Quick Reference: Common dictionary operations

OperationPurposeReturnsComplexityNotes
d[key]Get value by keyValueO(1) averageRaises KeyError if missing
d[key] = valueInsert or update keyNoneO(1) averageOverwrites if key exists
del d[key]Delete key-value pairNoneO(1) averageDeletes only
len(d)Number of itemsintO(1)Very common
key in dMembership check by keyboolO(1) averageChecks keys, not values
value in d.values()Membership check by valueboolO(n)Values require scan
for key in dIterate keysIterator behaviorO(n) totalDefault iteration is over keys
for key, value in d.items()Iterate key-value pairsIterator behaviorO(n) totalMost common iteration pattern
dict(iterable_or_mapping)Create dict from mapping / pairsNew dictO(n)Common conversion
{k: v for ...}Dictionary comprehensionNew dictO(n)Very common in interviews
`d1d2`Merge dictionariesNew dictO(n + m)
`d1= d2`Merge into existing dictNoneO(m)
{**d1, **d2}Merge via unpackingNew dictO(n + m)Common in older code
sorted(d)Sorted list of keysNew listO(n log n)Dict iteration is by key
sorted(d.items(), key=...)Sort items with custom ruleNew listO(n log n)Common interview pattern
reversed(d)Reverse insertion-order keysIteratorO(n) totalModern Python

Example

user = {
    "name": "Ruben",
    "age": 30
}

Here:

  • "name" is the key
  • "Ruben" is the value

Why dictionaries are powerful

The main reason dictionaries are so important is fast lookup.

user["name"]   # "Ruben"

Average lookup complexity:

O(1)

This is one of the most common interview questions.

Dictionaries are used constantly in:

  • API payloads
  • JSON data
  • frequency counting
  • grouping
  • caching
  • lookup tables
  • object mappings by ID

1.4.2 How lookup works conceptually

Python dictionaries use hashing.

Mental model

key -> hash -> bucket -> value

Example

prices = {
    "apple": 2,
    "banana": 1
}

When you do:

prices["apple"]

Python:

  1. hashes "apple"
  2. finds the bucket
  3. retrieves the value

This is why lookup is usually constant time.

A strong verbal answer should mention hash table / hash map.


1.4.3 Important dictionary methods

These are the core methods you need to understand.

get()

Safe access to dictionary values — returns None if key missing (instead of raising KeyError).

user = {"name": "Alice", "age": 30}
user.get("name")      # "Alice"
user.get("email")     # None
user.get("email", "unknown")  # "unknown"

This is one of the most important dictionary methods for avoiding crashes.

Complexity: O(1)

Interview context: Essential for avoiding KeyError. Very common in production code.


keys() / values() / items()

Get keys, values, or key-value pairs.

user = {"name": "Alice", "age": 30}
user.keys()      # dict_keys(['name', 'age'])
user.values()    # dict_values(['Alice', 30])
user.items()     # dict_items([('name', 'Alice'), ('age', 30)])

Most Pythonic iteration pattern:

for key, value in user.items():
    print(key, value)

This is much preferred over user.keys() when you need both.

Complexity: O(1) to get the view, O(n) to iterate.


pop()

Remove a key and return its value.

value = user.pop("age")
print(value)   # 30
print(user)    # {"name": "Alice"}

Raises KeyError if key doesn't exist:

user.pop("email")  # KeyError

Safe version with default:

user.pop("email", None)  # Returns None if missing

Complexity: O(1)

Interview use: Useful when you need to extract and process values.


setdefault()

Get a value, or set it to a default if missing. Very useful for grouping and frequency counting.

user = {"name": "Alice"}
user.setdefault("age", 0)    # Returns 0, adds "age": 0 to dict
user.setdefault("name", "Unknown")  # Returns "Alice" (already exists)

Very useful for frequency counting:

counts = {}
words = ["apple", "banana", "apple"]

for word in words:
    counts[word] = counts.get(word, 0) + 1  # Alternative to setdefault
    # Or: counts.setdefault(word, 0) += 1

Extremely useful for grouping:

people_by_city = {}
for person in people:
    city = person["city"]
    people_by_city.setdefault(city, []).append(person)

Complexity: O(1)

Interview context: Shows up constantly in grouping problems.


update()

Merge another dictionary into this one.

user = {"name": "Alice"}
user.update({"age": 30, "city": "NYC"})
print(user)  # {"name": "Alice", "age": 30, "city": "NYC"}

Overwrites existing keys:

user = {"name": "Alice", "age": 25}
user.update({"age": 30})
print(user)  # {"name": "Alice", "age": 30}

Complexity: O(k) where k = items being added.


clear()

Remove all items from dictionary.

user = {"name": "Alice", "age": 30}
user.clear()
print(user)  # {}

Complexity: O(n)


Membership with in

Check if a key exists (checks keys, not values).

"name" in user        # True
"email" in user       # False
"Alice" in user       # False (checks keys, not values)

This is the preferred way over get() when you just need a boolean.

Complexity: O(1)


1.4.4 Dictionary comprehensions

One of the most important Pythonic features.

Basic syntax

squares = {x: x * x for x in range(5)}

Result

{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

This is the dictionary equivalent of list comprehensions.

Mental model

Equivalent to:

squares = {}

for x in range(5):
    squares[x] = x * x

A strong interview answer should understand both forms.

Conditional dictionary comprehension

evens = {x: x * x for x in range(10) if x % 2 == 0}

Result

{0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

Very useful for transforming or filtering data in one line.


1.4.5 Important dictionary patterns

These patterns appear in almost every interview:

  1. Frequency counting: counts[key] = counts.get(key, 0) + 1
  2. Grouping: grouped.setdefault(key, []).append(item)
  3. Lookup by ID: by_id = {item.id: item for item in items}
  4. Safe access: value = d.get(key, default)
  5. Transformation: new_d = {k: transform(v) for k, v in d.items()}

Verbal interview questions

Answer these out loud:

  • Why is dictionary lookup usually O(1)?
  • Why use get() instead of direct indexing?
  • Explain items()
  • Explain dictionary comprehension
  • When would you use a dictionary over a list?

Coding drills

Drill 1: word frequency

def count_words(words: list[str]) -> dict[str, int]:
    ...

Drill 2: group by city

def group_by_city(people: list[dict]) -> dict[str, list]:
    ...

Drill 3: invert mapping

def invert(d: dict[str, int]) -> dict[int, str]:
    ...

Example

{"a": 1, "b": 2} -> {1: "a", 2: "b"}

1.4.6 Common interview problems

Dictionaries solve many interview problems elegantly.

Problem 1: Two Sum

Question: Given a list of integers and a target, find two numbers that sum to the target. Return their indices.

def twoSum(nums: list[int], target: int) -> list[int]:
    # nums = [2, 7, 11, 15], target = 9
    # Return [0, 1]  (2 + 7 = 9)
    ...

Key insight: Use dictionary to store number→index mapping. For each number, look up if (target - number) exists.

num_map = {}
for i, num in enumerate(nums):
    complement = target - num
    if complement in num_map:
        return [num_map[complement], i]
    num_map[num] = i
return []

Complexity: O(n) time, O(n) space. Much better than brute force O(n²).


Problem 2: Valid Anagram

Question: Given two strings, determine if they are anagrams (same characters, different order).

def isAnagram(s: str, t: str) -> bool:
    # s = "anagram", t = "nagaram" → True
    # s = "rat", t = "car" → False
    ...

Key insight: Count character frequencies. If both strings have same frequency map, they're anagrams.

from collections import Counter
return Counter(s) == Counter(t)

Complexity: O(n) time, O(1) space (only 26 letters).


Problem 3: Group Anagrams

Question: Given a list of strings, group anagrams together.

def groupAnagrams(strs: list[str]) -> list[list[str]]:
    # strs = ["eat", "tea", "ate", "nat", "tan", "bat"]
    # Return [["eat", "tea", "ate"], ["nat", "tan"], ["bat"]]
    ...

Key insight: Use sorted string as key—anagrams have the same sorted key.

groups = {}
for word in strs:
    key = ''.join(sorted(word))  # "eat", "tea", "ate" all → "aet"
    groups.setdefault(key, []).append(word)
return list(groups.values())

Complexity: O(n * k log k) where n = words, k = avg word length.


Problem 4: Top K Frequent Elements

Question: Count word frequencies, find the k most frequent.

def topKFrequent(words: list[str], k: int) -> list[str]:
    # words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
    # k = 2
    # Return ["apple", "banana"]  (apple: 3, banana: 2)
    ...

Key insight: Use Counter to count frequencies, then get top k.

from collections import Counter
counts = Counter(words)
return [word for word, _ in counts.most_common(k)]

Complexity: O(n + m log k) where m = unique words.


Problem 5: Majority Element

Question: Find the element that appears more than n/2 times.

def majorityElement(nums: list[int]) -> int:
    # nums = [3, 2, 3, 1, 3]
    # Return 3 (appears 3 times, more than 5/2)
    ...

Key insight: Count frequencies, return element with max count.

counts = {}
for num in nums:
    counts[num] = counts.get(num, 0) + 1
return max(counts, key=counts.get)

Complexity: O(n) time, O(n) space.


Problem 6: Ransom Note

Question: Can you create a ransom note from magazine letters (each letter used at most once)?

def canConstruct(ransomNote: str, magazine: str) -> bool:
    # ransomNote = "a", magazine = "abc" → True
    # ransomNote = "aa", magazine = "abc" → False
    ...

Key insight: Count available letters in magazine, verify each ransom letter is available.

available = {}
for char in magazine:
    available[char] = available.get(char, 0) + 1

for char in ransomNote:
    if available.get(char, 0) == 0:
        return False
    available[char] -= 1

return True

Complexity: O(n + m) where n = ransom length, m = magazine length.


Problem 7: Longest Consecutive Sequence

Question: Find the length of the longest consecutive elements sequence.

def longestConsecutive(nums: list[int]) -> int:
    # nums = [100, 4, 200, 1, 3, 2]
    # Return 4  (sequence is [1, 2, 3, 4])
    ...

Key insight: Use set for O(1) lookup. For each number, check if it's start of a sequence.

num_set = set(nums)
longest = 0

for num in num_set:
    if num - 1 not in num_set:  # Start of sequence
        length = 1
        while num + length in num_set:
            length += 1
        longest = max(longest, length)

return longest

Complexity: O(n) amortized (each number visited at most twice).


Problem 8: First Unique Character

Question: Find the index of the first character that appears only once.

def firstUniqChar(s: str) -> int:
    # s = "leetcode" → 0 ('l' appears once)
    # s = "loveleetcode" → 2 ('v' is first unique)
    # s = "aabb" → -1
    ...

Key insight: Count frequencies, then find first with count = 1.

counts = {}
for char in s:
    counts[char] = counts.get(char, 0) + 1

for i, char in enumerate(s):
    if counts[char] == 1:
        return i

return -1

Complexity: O(n) time, O(1) space (only 26 letters).


Problem 9: Happy Number

Question: Does repeatedly summing squares of digits eventually reach 1, or enter a cycle?

def isHappy(n: int) -> bool:
    # n = 7 → True (7 → 49 → 97 → 130 → 10 → 1)
    # n = 2 → False (enters cycle)
    ...

Key insight: Use set to detect cycles.

def get_next(n):
    total = 0
    while n > 0:
        digit = n % 10
        total += digit ** 2
        n //= 10
    return total

seen = set()
while n != 1 and n not in seen:
    seen.add(n)
    n = get_next(n)

return n == 1

Complexity: O(k) where k = iterations before reaching 1 or cycle.


Problem 10: Isomorphic Strings

Question: Are two strings isomorphic? (Characters can be replaced to make them equal.)

def isIsomorphic(s: str, t: str) -> bool:
    # s = "egg", t = "add" → True (e→a, g→d)
    # s = "badc", t = "baba" → False
    ...

Key insight: Map characters bidirectionally. Each char in s maps to exactly one in t, and vice versa.

s_to_t = {}
t_to_s = {}

for c1, c2 in zip(s, t):
    if c1 in s_to_t and s_to_t[c1] != c2:
        return False
    if c2 in t_to_s and t_to_s[c2] != c1:
        return False
    s_to_t[c1] = c2
    t_to_s[c2] = c1

return True

Complexity: O(n) time, O(1) space (at most 26 characters).