How to store and retrieve data fast using a Hash Map.
Your server receives requests. You need to remember the data users send—tasks in this case. Where do you put it?
Option 1: Search through an Array every time → slow ❌
Option 2: Use a Map for instant lookup → fast ✓
This step teaches you to build a simple data store.
A TaskStore that:
- Stores tasks in a JavaScript
Map - Creates a new task (add to Map)
- Retrieves a task by ID (lookup in Map)
- Updates a task (find and modify)
- Deletes a task (remove from Map)
- Lists all tasks
Imagine you have 100,000 tasks. You want task #99,999.
Array lookup:
const all_tasks = [];
const find_task = all_tasks.find((t) => t.id === "99999");
// Checks task 1, task 2, task 3... task 99999. ~100,000 checks = O(N)Map lookup:
const store = new Map();
const find_task = store.get("99999");
// Direct access, instant = O(1)At scale, Map is orders of magnitude faster.
It exports a single instance that all parts of the server share. This is the Singleton pattern.
When you require a module in Node.js, it runs once. The exports are cached.
// Every require gets the SAME instance
const store = require("./TaskStore.js"); // First import: runs constructor
const store2 = require("./TaskStore.js"); // Second import: returns SAME instance, not newSo when server.js adds a task, and later route handler reads it—they're both using the SAME data store.
create(taskData) {
const id = crypto.randomUUID();
const task = { id, ...taskData, createdAt: new Date().toISOString() };
this.tasks.set(id, task); // O(1) insertion
return task;
}- Generate unique ID
- Store in Map
- Return the new task
getById(id) {
return this.tasks.get(id); // O(1) lookup
}- Direct lookup, instant
getAll() {
return Array.from(this.tasks.values()); // O(N) to convert, but only happens once
}- Return array of all tasks
- This is O(N) because you must check all entries at least once
update(id, changes) {
const task = this.tasks.get(id); // O(1) lookup
Object.assign(task, changes); // Update fields
return task;
}- Find by ID
- Modify the existing object
- Return updated task
delete(id) {
this.tasks.delete(id); // O(1) deletion
}- Remove from Map instantly
| Operation | Array | Map |
|---|---|---|
| Create | O(1) | O(1) |
| Get by ID | O(N) | O(1) |
| Update | O(N) | O(1) |
| Delete | O(N) | O(1) |
| Get all | O(N) | O(N) |
Takeaway: Map is better for anything involving IDs.
In this project, all data lives in memory. When the server restarts:
- The Map is empty
- All tasks are lost
This is fine for learning. In production, you'd use a database to persist data.
Task: Add a method to TaskStore:
count() {
return this.tasks.size; // Return how many tasks exist
}Test it:
curl http://localhost:3000/tasks/countShould return something like:
{ "count": 5 }Every database (PostgreSQL, MongoDB, Redis) uses similar structures internally:
- Hash maps for fast ID lookups
- Indices for quick search
- Caching layers to avoid re-fetching
Learning data structures now helps you understand database design later.
You can now store and retrieve tasks. But what if you have 1,000,000 tasks and only want the 10 most important ones?
That's where Priority Queues come in.