Imagine this: you’re optimizing your JavaScript code and you notice something odd. You’re using a Map to store some configuration settings or feature flags, but when you benchmark it against a plain object, it feels slower.
Both Map and Object provide O(1) lookups, so what’s happening under the hood? Let’s break it down.
The Common Misconception
Many developers assume that Map is the modern, better alternative to Object for all key-value storage. And for certain cases, it absolutely is.
But here’s the thing: if your keys are strings and the set of keys is relatively small and fixed, an Object is almost always faster. Why? Let’s dive into the mechanics.
Objects vs. Maps: The Basics
Consider this scenario: you have a set of API endpoints your application uses.
// Using an Object
const endpoints = {
login: '/api/login',
logout: '/api/logout',
profile: '/api/profile'
};
// Using a Map
const endpointsMap = new Map([
['login', '/api/login'],
['logout', '/api/logout'],
['profile', '/api/profile']
]);
Accessing a value:
endpoints['login']; // Object property access
endpointsMap.get('login'); // Map lookup
On the surface, both do the same thing: give you the value for a key. But how they achieve it internally is different.
What Happens Under the Hood
Objects use hidden classes
Modern JavaScript engines, like V8, optimize objects heavily. When you create an object:
const endpoints = { login: '/api/login', logout: '/api/logout' };
- The engine builds a
hidden classfor the object — think of it as a lightweight blueprint, like a C struct. - Each property gets a
fixed memory offset. - Accessing a property is basically a
direct pointer lookup. Blazing fast.
Maps use generic hash tables
When you create a Map:
const endpointsMap = new Map([['login', '/api/login'], ['logout', '/api/logout']]);
- The engine can’t assume fixed property types.
- Keys can be anything — strings, numbers, objects, even
NaN. - Every
.get()involves:- Hashing the key.
- Finding the appropriate bucket.
- Comparing keys for equality.
- Even though this is still O(1) on average, the constant factor is higher.
Seeing the Difference in a Benchmark
Let’s test it with a simple benchmark:
const obj = {
login: '/api/login',
logout: '/api/logout',
profile: '/api/profile'
};
const map = new Map(Object.entries(obj));
const keys = ['login', 'logout', 'profile'];
console.time('Object');
for (let i = 0; i < 1e7; i++) obj[keys[i % 3]];
console.timeEnd('Object');
console.time('Map');
for (let i = 0; i < 1e7; i++) map.get(keys[i % 3]);
console.timeEnd('Map');
Typical results on Node.js:
Object: 50 ms
Map: 180 ms
Even though both are O(1), the object access is significantly faster in practice because of engine optimizations.
When Map Is Actually Better
That said, Map isn’t bad — it’s just optimized for different scenarios:
- Non-string keys: Like objects or functions.
const userRoles = new Map();
userRoles.set(userObj, 'admin');
- Dynamic key sets: When keys are added or removed frequently.
- Guaranteed iteration order: Maps preserve insertion order.
- Large datasets with unknown keys: Maps scale better than objects when used as true hash tables.
Quick Comparison
| Aspect | Object | Map |
|---|---|---|
| Key Types | Strings / Symbols | Any value |
| Lookup Speed | Faster (hidden classes) | Slower (hash + compare) |
| Memory | Lower | Higher |
| Dynamic Resizing | Poor | Excellent |
| Ideal Use Case | Small, static, string-keyed data | Dynamic or complex key-value pairs |
TL;DR
-
Objectlookups are JIT-optimized (hidden classes + inline caching). -
Maplookups are generic hash table operations — flexible but slightly slower. - For small, fixed, string-keyed datasets → Object wins.
- For dynamic or complex keys → Map shines.
Closing Thoughts
Sometimes, the “modern” API isn’t the fastest — it’s just more flexible.
Understanding what happens under the hood helps you write code that is both clean and performant.
So next time you reach for Map by default, ask yourself: do I really need its flexibility, or is a simple object enough?
Top comments (0)