Hash-Based Design: Building Smart Memory Systems 🧠
The Story of the Magical Library
Imagine you’re a librarian in a HUGE library with millions of books. Every day, hundreds of people ask you:
“Where is the book about dragons?”
If you had to walk through every shelf to find it… you’d never finish! 😫
But what if you had a magic filing system? You whisper “dragons” and instantly know: Shelf 42, Row 3!
That’s exactly what Hash Tables do for computers. They’re the magic filing system that finds things INSTANTLY!
What We’ll Build Together
graph TD A["Hash-Based Design"] --> B["HashMap"] A --> C["HashSet"] A --> D["LRU Cache"] B --> E["Store Key-Value Pairs"] C --> F["Store Unique Items"] D --> G["Remember Recent Things"]
Today, we’ll design THREE powerful tools:
- HashMap - A dictionary that finds words instantly
- HashSet - A guest list that never has duplicates
- LRU Cache - A smart brain that remembers important stuff
Part 1: Design HashMap 📚
What is a HashMap?
Think of a HashMap like a magical address book.
| What You Give | What You Get Back |
|---|---|
| Friend’s name | Their phone number |
| Word | Its meaning |
| Student ID | Student info |
The Magic: You don’t search page by page. You ask for “Emma” and BOOM - you get her number!
How Does the Magic Work?
Step 1: The Hash Function (The Magic Spell ✨)
Every name gets turned into a NUMBER using a special spell:
// Simple hash function
int hash(String key) {
int number = 0;
for (char c : key.toCharArray()) {
number += c; // Add letter values
}
return number % arraySize;
}
Example:
- “CAT” → C(67) + A(65) + T(84) = 216
- If array size is 10: 216 % 10 = 6
- So “CAT” lives at position 6!
Step 2: The Buckets (Storage Boxes 📦)
graph TD subgraph HashMap B0["Bucket 0"] B1["Bucket 1"] B2["Bucket 2"] B3["Bucket 3"] end B0 --> N1["apple→red"] B2 --> N2["cat→meow"] B2 --> N3["act→play"]
Sometimes two keys get the same number (like “cat” and “act”). That’s called a collision. We solve it by chaining items together!
Let’s Build It! 🔨
The Blueprint
class MyHashMap {
// Our storage: array of linked lists
LinkedList<int[]>[] buckets;
int size = 1000; // 1000 buckets
public MyHashMap() {
buckets = new LinkedList[size];
}
}
The Three Main Powers
Power 1: PUT (Store something)
void put(int key, int value) {
int index = key % size;
// Create bucket if empty
if (buckets[index] == null) {
buckets[index] = new LinkedList<>();
}
// Update if key exists
for (int[] pair : buckets[index]) {
if (pair[0] == key) {
pair[1] = value;
return;
}
}
// Add new pair
buckets[index].add(new int[]{key, value});
}
Power 2: GET (Find something)
int get(int key) {
int index = key % size;
if (buckets[index] == null) {
return -1; // Not found!
}
for (int[] pair : buckets[index]) {
if (pair[0] == key) {
return pair[1]; // Found it!
}
}
return -1; // Not in this bucket
}
Power 3: REMOVE (Delete something)
void remove(int key) {
int index = key % size;
if (buckets[index] == null) return;
buckets[index].removeIf(
pair -> pair[0] == key
);
}
Real-Life Example 🌟
MyHashMap phonebook = new MyHashMap();
// Store contacts
phonebook.put(101, 5551234); // ID 101
phonebook.put(102, 5555678); // ID 102
// Find Emma's number
int emmaNumber = phonebook.get(101);
// Returns: 5551234
// Emma changed her number
phonebook.put(101, 5559999);
// Remove old contact
phonebook.remove(102);
Part 2: Design HashSet 🎯
What is a HashSet?
A HashSet is like a VIP guest list at a party.
Rules:
- Each person can only be on the list ONCE
- You can quickly check if someone is invited
- You can add or remove guests
graph LR A["Guest List"] --> B{Is Emma here?} B -->|Yes| C["Already invited!"] B -->|No| D["Add her name"]
The Secret: It’s Just a HashMap… Without Values!
Think about it:
- HashMap stores:
key → value - HashSet stores:
key → (nothing)
We only care about the KEY existing!
Let’s Build It! 🔧
class MyHashSet {
LinkedList<Integer>[] buckets;
int size = 1000;
public MyHashSet() {
buckets = new LinkedList[size];
}
// Add a guest (if not already there)
void add(int key) {
int index = key % size;
if (buckets[index] == null) {
buckets[index] = new LinkedList<>();
}
if (!buckets[index].contains(key)) {
buckets[index].add(key);
}
}
// Is this guest on the list?
boolean contains(int key) {
int index = key % size;
if (buckets[index] == null) {
return false;
}
return buckets[index].contains(key);
}
// Remove a guest
void remove(int key) {
int index = key % size;
if (buckets[index] != null) {
buckets[index].remove(
Integer.valueOf(key)
);
}
}
}
Real-Life Example 🎉
MyHashSet vipList = new MyHashSet();
// Add guests
vipList.add(1); // Emma joins
vipList.add(2); // Tom joins
vipList.add(1); // Emma tries again - ignored!
// Check the door
vipList.contains(1); // true - Emma is VIP!
vipList.contains(3); // false - stranger!
// Remove Tom (he was rude)
vipList.remove(2);
Part 3: LRU Cache 🧠
What is LRU Cache?
LRU = Least Recently Used
Imagine your brain can only remember 5 phone numbers. When you learn a 6th number, you forget the one you haven’t used the longest!
graph TD A["New Memory"] --> B{Brain Full?} B -->|No| C["Just add it!"] B -->|Yes| D["Forget oldest unused"] D --> C
The Two Superpowers Combined! 💪
To build LRU Cache, we need:
| Tool | What It Does |
|---|---|
| HashMap | Find things instantly |
| Doubly Linked List | Track what was used recently |
graph LR subgraph Linked List Order A["Most Recent"] --> B --> C --> D["Least Recent"] end subgraph HashMap Quick Access H["HashMap"] -.-> A H -.-> B H -.-> C H -.-> D end
The Clever Trick 🎩
Doubly Linked List = Each item knows its neighbors!
[HEAD] ↔ [Item A] ↔ [Item B] ↔ [Item C] ↔ [TAIL]
- HEAD: Fake start marker
- TAIL: Fake end marker
- New stuff goes after HEAD (most recent)
- Old stuff near TAIL gets removed
Let’s Build It! 🏗️
The Node (Each Memory Cell)
class Node {
int key;
int value;
Node prev;
Node next;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
The LRU Cache Class
class LRUCache {
int capacity;
Map<Integer, Node> map;
Node head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
// Create dummy head and tail
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
}
}
The Helper Methods
// Add node right after HEAD (most recent)
void addToFront(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
// Remove a node from anywhere
void removeNode(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// Move existing node to front
void moveToFront(Node node) {
removeNode(node);
addToFront(node);
}
GET - Access a Memory
int get(int key) {
if (!map.containsKey(key)) {
return -1; // Don't remember this!
}
Node node = map.get(key);
moveToFront(node); // I just used this!
return node.value;
}
PUT - Store a Memory
void put(int key, int value) {
// Already exists? Update it
if (map.containsKey(key)) {
Node node = map.get(key);
node.value = value;
moveToFront(node);
return;
}
// Brain full? Forget oldest
if (map.size() >= capacity) {
Node oldest = tail.prev;
removeNode(oldest);
map.remove(oldest.key);
}
// Add new memory
Node newNode = new Node(key, value);
addToFront(newNode);
map.put(key, newNode);
}
Real-Life Example 🌟
// Brain that remembers 3 things
LRUCache brain = new LRUCache(3);
brain.put(1, 100); // Remember A
brain.put(2, 200); // Remember B
brain.put(3, 300); // Remember C
// Order: [3, 2, 1]
brain.get(1); // Used A!
// Order: [1, 3, 2]
brain.put(4, 400); // Remember D
// Brain full! Forget B (least recent)
// Order: [4, 1, 3]
brain.get(2); // Returns -1
// "Who? I don't remember B!"
Why LRU Cache is AMAZING 🚀
| Operation | Time |
|---|---|
| GET | O(1) - Instant! |
| PUT | O(1) - Instant! |
Compare to searching through everything: O(n) - Slow! 🐢
Summary: Your New Superpowers! 💫
graph LR A["Hash-Based Design"] --> B["HashMap"] A --> C["HashSet"] A --> D["LRU Cache"] B --> B1["Fast key-value lookup"] B --> B2["O of 1 operations"] C --> C1["No duplicates"] C --> C2["Fast membership check"] D --> D1["Limited memory"] D --> D2["Forgets unused items"] D --> D3["HashMap + LinkedList"]
Key Takeaways
- HashMap = Magic dictionary with instant lookup
- HashSet = HashMap without values (just checking existence)
- LRU Cache = Smart memory that forgets old unused things
- Hash Function = Turns keys into array positions
- Collision Handling = Chain items when positions collide
You Did It! 🎊
You now understand how to design:
- ✅ Your own HashMap
- ✅ Your own HashSet
- ✅ Your own LRU Cache
These are REAL interview questions at top tech companies. You just learned what many struggle to understand!
Remember: The magic isn’t in memorizing code. It’s in understanding WHY it works. Now you do! 🌟
