A hash table (or hash map) is the data structure behind Python's own dict — it gives average O(1) insert, lookup, and delete by converting a key into an array index using a hash function. Understanding how one works under the hood is a common interview topic and genuinely useful once you need a custom key-value store with different collision behavior than the built-in dict.
The two things any hash table needs are a hash function that maps a key to a bucket index, and a strategy for handling collisions — when two different keys hash to the same bucket. Here we use separate chaining: each bucket holds a small list of (key, value) pairs, so a collision just means appending to that bucket's list instead of overwriting anything.
Python Program to Implement a Hash Table from Scratch
class HashTable:
def __init__(self, size=8):
self.size = size
self.buckets = [[] for _ in range(size)]
def _hash(self, key):
# Convert the key into a bucket index
return hash(key) % self.size
def insert(self, key, value):
bucket = self.buckets[self._hash(key)]
for i, (k, v) in enumerate(bucket):
if k == key:
bucket[i] = (key, value) # update existing key
return
bucket.append((key, value))
def get(self, key):
bucket = self.buckets[self._hash(key)]
for k, v in bucket:
if k == key:
return v
raise KeyError(key)
def delete(self, key):
bucket = self.buckets[self._hash(key)]
for i, (k, v) in enumerate(bucket):
if k == key:
del bucket[i]
return
raise KeyError(key)
ht = HashTable()
ht.insert("name", "Alice")
ht.insert("age", 30)
ht.insert("city", "Paris")
print(ht.get("name"))
print(ht.get("age"))
ht.delete("age")
try:
ht.get("age")
except KeyError:
print("age was deleted")
Output:
Alice
30
age was deleted
Each bucket is a small Python list, so looking inside one is just iterating over a list of the (usually one or two) pairs stored there. With a good hash function and enough buckets, each bucket stays small and insert/get/delete all average O(1) — the real dict in CPython works on this same principle, with open addressing instead of chaining and a lot of low-level optimization. If a bucket grows too large (many collisions), performance degrades toward O(n) for that bucket, which is why production hash tables automatically resize and rehash as they fill up.
