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
// 1. Remove duplicates — the classic
const ids = [1, 2, 3, 2, 1, 4];
const uniqueIds = [...new Set(ids)]; // [1, 2, 3, 4]
// 2. Fast membership checking
const premiumUsers = new Set(["user1", "user2", "user3"]);
if (premiumUsers.has(currentUser.id)) {
// O(1) lookup instead of Array.includes() which is O(n)
showPremiumContent();
}
// 3. Set operations
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...setA, ...setB]); // {1,2,3,4,5,6}
// Intersection
const intersection = new Set([...setA].filter(x => setB.has(x))); // {3,4}
// Difference
const difference = new Set([...setA].filter(x => !setB.has(x))); // {1,2}
// 4. Tracking visited/seen items
const visited = new Set();
function crawlPage(url) {
if (visited.has(url)) return; // Already crawled
visited.add(url);
// ... crawl
}Production Patterns
Pattern: Feature Flags with Set
const enabledFeatures = new Set(["dark-mode", "new-editor", "ai-chat"]);
function FeatureGate({ feature, children }) {
if (!enabledFeatures.has(feature)) return null;
return children;
}
// UsagePerformance Comparison — Real Numbers
// Lookup performance with 100,000 items:
// Array.includes() — ~2.5ms
// Set.has() — ~0.01ms
// Object["key"] — ~0.01ms
// Map.get("key") — ~0.01ms
// If you're doing lookups in a loop, Set/Map is 250x faster than ArrayBest 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)