Written by Rahul · Frontend Engineer at Google · Updated 2025
I see developers defaulting to Objects and Arrays for everything. That's like using a hammer for every job. JavaScript gives us 4 main data structures — each optimized for different use cases. Let me show you when to use which one.
Quick Decision Guide
| Need | Use |
|---|---|
| Ordered list of items | Array |
| Key-value pairs (string keys) | Object |
| Key-value pairs (any key type) | Map |
| Unique values only | Set |
Object vs Map — The Key Differences
| Feature | Object | Map |
|---|---|---|
| Key types | String/Symbol only | Any type (objects, functions, etc.) |
| Key order | Not guaranteed (mostly insertion) | Guaranteed insertion order |
| Size | Object.keys(obj).length | map.size (O(1)) |
| Iteration | for...in, Object.entries() | for...of, .forEach() |
| Performance | Slower for frequent add/delete | Optimized for frequent add/delete |
| JSON support | ✅ Native | ❌ Need manual conversion |
| Prototype | Has inherited properties | Clean — no inherited keys |
When to Use Map
// 1. When keys aren't strings
const componentCache = new Map();
const buttonRef = document.querySelector("button");
componentCache.set(buttonRef, { clicks: 0, renders: 5 });
// 2. When you need to know the size instantly
const userSessions = new Map();
console.log(userSessions.size); // O(1) — Object needs Object.keys().length
// 3. When you add/delete keys frequently
const cache = new Map();
cache.set("user:123", userData);
cache.delete("user:123");
// Map is optimized for this pattern
// 4. LRU Cache implementation
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map(); // Map preserves insertion order!
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value); // Move to end (most recent)
return value;
}
put(key, value) {
this.cache.delete(key);
this.cache.set(key, value);
if (this.cache.size > this.capacity) {
// Delete the FIRST (oldest) entry
this.cache.delete(this.cache.keys().next().value);
}
}
}When to Use Object
// 1. API responses — they're already objects
const user = await fetch("/api/user").then(r => r.json());
// 2. Configuration / options
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
// 3. When you need JSON serialization
JSON.stringify(config); // Works naturally
// JSON.stringify(map); // Just gives "{}"Array vs Set — The Key Differences
| Feature | Array | Set |
|---|---|---|
| Duplicates | ✅ Allowed | ❌ Automatically unique |
Lookup (.has()) | O(n) — .includes() | O(1) — .has() |
| Order | Indexed | Insertion order |
| Access by index | ✅ arr[0] | ❌ No direct index access |
When to Use Set
Production Patterns
Pattern: Feature Flags with Set
Performance Comparison — Real Numbers
Best Practices
- Default to Array for ordered collections you'll iterate over
- Use Set when uniqueness matters or you need fast lookups
- Use Map when you need non-string keys or frequent add/delete
- Use Object for structured data with known keys (configs, API responses)
- Convert between them freely —
[...set],new Set(arr),Object.fromEntries(map)