Dicts and sets are the two hash-table-backed collection types in Python. A dict maps keys to values and gives O(1) lookup. A set stores unique elements and gives O(1) membership tests. This lesson walks through creation, common operations, comprehensions, and the patterns that show up in real code.
Creating a dict
Use curly braces with key-value pairs, the dict() constructor with keyword arguments, or a dict comprehension. Keys must be hashable. Lists and other dicts cannot be keys. Strings, numbers, tuples, and frozensets are all valid.
user = {"name": "Asha", "role": "admin", "age": 28}
# dict() with keyword arguments
user = dict(name="Asha", role="admin", age=28)
# From a list of pairs
pairs = [("name", "Asha"), ("role", "admin"), ("age", 28)]
user = dict(pairs)
# Dict comprehension
squares = {n: n * n for n in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Essential dict methods
Dicts ship with get, setdefault, update, pop, items, keys, and values. get returns a default when the key is missing, which is the cleanest way to fetch a value that may not exist. setdefault inserts a default only if the key is absent.
user = {"name": "Asha", "role": "admin"}
print(user.get("age", 0)) # 0 (default)
print(user["age"]) # KeyError
# setdefault for missing-key initialization
user.setdefault("age", 0)
user["age"] += 1
print(user) # {'name': 'Asha', 'role': 'admin', 'age': 1}
# Merge two dicts (3.9+)
extra = {"city": "Pune", "role": "owner"}
merged = user | extra
print(merged) # {'name': 'Asha', 'role': 'owner', 'age': 1, 'city': 'Pune'}
Iterating over a dict
Iterating a dict gives you its keys. Use .items() for key and value together, and .values() when you only need the values. Insertion order is preserved since Python 3.7.
prices = {"apple": 30, "banana": 10, "cherry": 60}
for key in prices:
print(key) # apple banana cherry
for key, value in prices.items():
print(f"{key}: {value}")
# apple: 30
# banana: 10
# cherry: 60
Creating and using sets
A set is an unordered collection of unique elements. Use a set to deduplicate or to test membership in O(1). Set literals use curly braces with elements, but {} is the empty dict, not the empty set. Use set() to make an empty set.
nums = {1, 2, 3, 2, 1, 4, 5}
print(nums) # {1, 2, 3, 4, 5}
empty = set() # not {}
# Deduplicate a list
unique = list(set(["a", "b", "a", "c", "b"]))
print(unique) # ['a', 'b', 'c'] (order not guaranteed)
# Membership test
fruits = {"apple", "banana", "cherry"}
print("apple" in fruits) # True
print("grape" in fruits) # False
Set operations
Sets support the mathematical operations you would expect. Union, intersection, difference, and symmetric difference all return new sets. Use the operators or the named methods.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # union: {1, 2, 3, 4, 5, 6}
print(a & b) # intersection: {3, 4}
print(a - b) # difference: {1, 2}
print(a ^ b) # symmetric difference: {1, 2, 5, 6}
Counter and defaultdict from collections
The collections module ships with two high-value helpers. Counter counts hashable items. defaultdict returns a default value for missing keys, which removes boilerplate when building dicts of lists or sets.
from collections import Counter, defaultdict
# Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)
print(counts) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(counts.most_common(2)) # [('apple', 3), ('banana', 2)]
# defaultdict
groups = defaultdict(list)
for name, team in [("Asha", "red"), ("Bala", "blue"), ("Cyril", "red")]:
groups[team].append(name)
print(dict(groups)) # {'red': ['Asha', 'Cyril'], 'blue': ['Bala']}
Common pitfalls with dicts and sets
Three mistakes appear repeatedly when working with hash-table-backed collections. First, accessing a missing key with square brackets raises KeyError, which crashes the program; use dict.get(key, default) when the absence is expected. Second, iterating a dict while mutating it raises RuntimeError: dictionary changed size during iteration. Either collect the keys you want to delete into a list first, or build a new dict with a comprehension. Third, putting a mutable object (a list or dict) inside a set or as a dict key raises TypeError: unhashable type because only immutable objects are hashable. If you really need a list-like key, convert it to a tuple or to a string with json.dumps.
Comparison with JavaScript Map, Set, and Object
Python's dict, set, and JavaScript's Map, Set, Object are similar in spirit but differ in important details. JavaScript Object keys are always strings (or Symbols), while Python dict keys can be any hashable type, including tuples and frozensets. JavaScript Set has no literal empty-set syntax either; new Set() works, but {} is reserved for plain objects. Python 3.7+ guarantees insertion order in dicts, but JavaScript objects historically did not guarantee key order for non-integer string keys; Map does, and that is the better comparison. The Python equivalent of structured cloning is pickle.dumps for arbitrary objects, but it is rarely the right tool for inter-process data; reach for json.dumps and a serialisable schema.
Performance notes for hash tables
Python dicts and sets are open-addressing hash tables with a load factor around two-thirds. Lookups, insertions, and deletions are amortised O(1), but the constant factor depends on the quality of the hash function. Built-in types like int, str, and tuple hash well; custom classes get a default id-based hash that is fine for tests but produces degenerate behaviour if your objects are large. Define __hash__ and __eq__ together, and prefer a hash that depends only on the immutable fields of the object. For very large dicts of small strings, sys.intern reuses the string objects themselves so equality checks turn into identity checks, saving both memory and time.
Summary
Use dicts for key-to-value mapping with O(1) lookup. Use sets for unique elements and O(1) membership tests. Reach for Counter and defaultdict when the basic dict gets verbose. The next lesson covers functions, the core unit of code organization in Python.