HashMap Implementation from Scratch in DSA Python - Time & Space Complexity
When building a HashMap from scratch, it's important to understand how fast it can add, find, or remove items.
We want to know how the time to do these actions changes as we store more items.
Analyze the time complexity of the following simple HashMap put operation.
class SimpleHashMap:
def __init__(self, size=10):
self.size = size
self.buckets = [[] for _ in range(size)]
def put(self, key, value):
index = hash(key) % self.size
bucket = self.buckets[index]
for i, (k, v) in enumerate(bucket):
if k == key:
bucket[i] = (key, value)
return
bucket.append((key, value))
This code stores key-value pairs in buckets using a hash function and handles collisions by storing multiple pairs in a list.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through the bucket list to find if the key exists.
- How many times: Up to the number of items in that bucket, which depends on how many keys hash to the same index.
As more items are added, buckets may hold more pairs, making the search inside a bucket longer.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 1 to 2 checks per put (few collisions) |
| 100 | About 5 to 10 checks per put (more collisions) |
| 1000 | About 50 to 100 checks per put (many collisions) |
Pattern observation: Without resizing, the time grows roughly proportional to the number of items per bucket, which grows as total items increase.
Time Complexity: O(n) in worst case, O(1) on average
This means usually adding or updating a key is very fast, but if many keys collide, it can slow down linearly with the number of items.
[X] Wrong: "HashMap operations always take the same constant time no matter what."
[OK] Correct: If many keys end up in the same bucket, the code must check each one, making the operation slower.
Understanding how collisions affect speed shows you know the real behavior of hash maps, a key skill for coding interviews.
"What if we resize and rehash the buckets when they get too full? How would the time complexity change then?"
Practice
HashMap in data structures?Solution
Step 1: Understand what a HashMap stores
A HashMap stores data as key-value pairs, allowing quick access to values using keys.Step 2: Identify the main purpose
The main purpose is to retrieve data quickly by keys, not to sort or calculate.Final Answer:
To store key-value pairs for fast data retrieval -> Option AQuick Check:
HashMap = fast key-value access [OK]
- Confusing HashMap with sorted data structures
- Thinking HashMap stores data sequentially
- Assuming HashMap is for calculations
HashMap class constructor in Python?Solution
Step 1: Identify the data structure for storing key-value pairs
A HashMap stores key-value pairs, so a dictionary{}is appropriate.Step 2: Check each option's data type
def __init__(self): self.map = {} uses{}which is a dictionary. Others use list, set, or tuple which are not suitable for key-value mapping.Final Answer:
def __init__(self): self.map = {} -> Option CQuick Check:
HashMap uses dictionary = {} [OK]
- Using list or tuple instead of dictionary
- Confusing set with dictionary
- Not initializing the map attribute
class HashMap:
def __init__(self):
self.map = {}
def put(self, key, value):
self.map[key] = value
def get(self, key):
return self.map.get(key, None)
h = HashMap()
h.put('a', 1)
h.put('b', 2)
print(h.get('a'))
print(h.get('c'))Solution
Step 1: Trace the put method calls
Keys 'a' and 'b' are added with values 1 and 2 respectively.Step 2: Trace the get method calls
Getting 'a' returns 1. Getting 'c' returns None because 'c' is not in the map and default is None.Final Answer:
1\nNone -> Option AQuick Check:
Existing key returns value, missing key returns None [OK]
- Expecting 0 instead of None for missing keys
- Confusing print output format
- Assuming error on missing key
HashMap implementation snippet:class HashMap:
def __init__(self):
self.map = []
def put(self, key, value):
self.map[key] = value
Solution
Step 1: Check the data structure used for self.map
self.map is initialized as a list, but lists do not support key-based assignment.Step 2: Understand why list causes error
Using self.map[key] = value will cause TypeError because list indices must be integers, not keys.Final Answer:
Using list instead of dictionary for self.map -> Option DQuick Check:
List can't use keys as indices [OK]
- Thinking put method needs return
- Confusing method names
- Ignoring data type of self.map
HashMap that handles collisions by chaining (using lists). Which of the following code snippets correctly initializes the internal storage for chaining?Solution
Step 1: Understand chaining collision handling
Chaining uses a list of lists where each bucket holds a list of key-value pairs.Step 2: Identify correct initialization
self.map = [[] for _ in range(size)] creates a list of empty lists, one for each bucket, suitable for chaining.Step 3: Why other options are incorrect
self.map = [None] * size creates a list of None, which needs extra checks. self.map = {} is a dictionary, not a list of buckets. self.map = [0] * size creates a list of zeros, not suitable for chaining.Final Answer:
self.map = [[] for _ in range(size)] -> Option BQuick Check:
Chaining needs list of lists = [[] for _ in range(size)] [OK]
- Using None or 0 instead of empty lists for buckets
- Confusing dictionary with list of buckets
- Not initializing buckets properly for chaining
Ready to prove it?
Pass the test and this pattern moves unknown
