JavaScript Maps are powerful data structures that often go underutilized by developers. Unlike regular objects, Maps provide a clean way to store key-value pairs with better performance characteristics and more flexible key types. Whether you're preparing for interviews or looking to level up your JavaScript skills, this comprehensive guide will help you master Maps through 30 practical questions and detailed explanations.
What is a JavaScript Map?
A Map is a built-in object that holds key-value pairs and remembers the original insertion order of keys. Unlike objects, Maps can use any type of value as keys, including functions, objects, and primitive types.
Key Differences Between Maps and Objects
Before diving into the questions, let's understand why Maps are special:
- Key Types: Maps can use any type as keys, while objects only use strings and symbols
-
Size: Maps have a
size
property, objects requireObject.keys().length
-
Iteration: Maps are directly iterable, objects need
Object.keys()
or similar methods - Performance: Maps perform better for frequent additions and removals
-
Prototype: Maps don't have default keys, objects inherit from
Object.prototype
30 Essential JavaScript Map Questions
Beginner Level (Questions 1-10)
1. How do you create a new Map?
// Empty Map
const map1 = new Map();
// Map with initial values
const map2 = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
console.log(map1); // Map(0) {}
console.log(map2); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}
2. How do you add a key-value pair to a Map?
const myMap = new Map();
myMap.set('name', 'John');
myMap.set('age', 30);
console.log(myMap); // Map(2) {'name' => 'John', 'age' => 30}
3. How do you get a value from a Map?
const myMap = new Map([['city', 'New York']]);
const city = myMap.get('city');
console.log(city); // 'New York'
console.log(myMap.get('country')); // undefined
4. How do you check if a key exists in a Map?
const myMap = new Map([['active', true]]);
console.log(myMap.has('active')); // true
console.log(myMap.has('inactive')); // false
5. How do you delete a key-value pair from a Map?
const myMap = new Map([
['temp', 'delete me'],
['keep', 'important']
]);
myMap.delete('temp');
console.log(myMap); // Map(1) {'keep' => 'important'}
console.log(myMap.delete('nonexistent')); // false
6. How do you get the size of a Map?
const myMap = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
console.log(myMap.size); // 3
7. How do you clear all entries from a Map?
const myMap = new Map([['a', 1], ['b', 2]]);
console.log(myMap.size); // 2
myMap.clear();
console.log(myMap.size); // 0
console.log(myMap); // Map(0) {}
8. Can you use objects as keys in a Map?
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const myMap = new Map();
myMap.set(obj1, 'First object');
myMap.set(obj2, 'Second object');
console.log(myMap.get(obj1)); // 'First object'
console.log(myMap.get({ id: 1 })); // undefined (different object reference)
9. How do you iterate over a Map using for...of?
const myMap = new Map([
['name', 'Alice'],
['age', 25],
['city', 'Boston']
]);
// Iterate over entries
for (const [key, value] of myMap) {
console.log(`${key}: ${value}`);
}
// Output:
// name: Alice
// age: 25
// city: Boston
10. What's the difference between Map.prototype.set() return value and object assignment?
const myMap = new Map();
const obj = {};
// Map.set() returns the Map instance (chainable)
myMap.set('a', 1).set('b', 2).set('c', 3);
// Object assignment returns the assigned value
obj.a = 1; // returns 1
obj.b = 2; // returns 2
console.log(myMap); // Map(3) {'a' => 1, 'b' => 2, 'c' => 3}
console.log(obj); // {a: 1, b: 2}
Intermediate Level (Questions 11-20)
11. How do you convert a Map to an array?
const myMap = new Map([
['x', 10],
['y', 20],
['z', 30]
]);
// Convert to array of [key, value] pairs
const entriesArray = Array.from(myMap);
console.log(entriesArray); // [['x', 10], ['y', 20], ['z', 30]]
// Alternative using spread operator
const spreadArray = [...myMap];
console.log(spreadArray); // [['x', 10], ['y', 20], ['z', 30]]
12. How do you get all keys from a Map?
const myMap = new Map([
['first', 'A'],
['second', 'B'],
['third', 'C']
]);
const keys = Array.from(myMap.keys());
console.log(keys); // ['first', 'second', 'third']
// Or iterate directly
for (const key of myMap.keys()) {
console.log(key);
}
13. How do you get all values from a Map?
const myMap = new Map([
['apple', 5],
['banana', 3],
['orange', 8]
]);
const values = Array.from(myMap.values());
console.log(values); // [5, 3, 8]
// Calculate total
const total = values.reduce((sum, val) => sum + val, 0);
console.log(total); // 16
14. How do you merge two Maps?
const map1 = new Map([
['a', 1],
['b', 2]
]);
const map2 = new Map([
['c', 3],
['d', 4]
]);
// Method 1: Using spread operator
const merged1 = new Map([...map1, ...map2]);
// Method 2: Using forEach
const merged2 = new Map(map1);
map2.forEach((value, key) => merged2.set(key, value));
console.log(merged1); // Map(4) {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}
15. How do you use forEach with a Map?
const myMap = new Map([
['product1', 29.99],
['product2', 49.99],
['product3', 19.99]
]);
myMap.forEach((value, key, map) => {
console.log(`${key}: $${value}`);
console.log(`Map size: ${map.size}`);
});
16. How do you convert an object to a Map?
const obj = {
name: 'Sarah',
age: 28,
profession: 'Developer'
};
// Method 1: Using Object.entries()
const map1 = new Map(Object.entries(obj));
// Method 2: Manual iteration
const map2 = new Map();
for (const key in obj) {
map2.set(key, obj[key]);
}
console.log(map1); // Map(3) {'name' => 'Sarah', 'age' => 28, 'profession' => 'Developer'}
17. How do you convert a Map back to an object?
const myMap = new Map([
['title', 'JavaScript Guide'],
['author', 'Jane Doe'],
['pages', 350]
]);
// Method 1: Using Object.fromEntries()
const obj1 = Object.fromEntries(myMap);
// Method 2: Manual approach
const obj2 = {};
myMap.forEach((value, key) => {
obj2[key] = value;
});
console.log(obj1); // {title: "'JavaScript Guide', author: 'Jane Doe', pages: 350}"
18. Can you use functions as keys in a Map?
function greet() { return 'Hello!'; }
function farewell() { return 'Goodbye!'; }
const functionMap = new Map();
functionMap.set(greet, 'greeting function');
functionMap.set(farewell, 'farewell function');
console.log(functionMap.get(greet)); // 'greeting function'
console.log(functionMap.size); // 2
// You can even call the function keys
functionMap.forEach((value, func) => {
console.log(`${func.name}: ${func()}`);
});
19. How do you check if a Map is empty?
const emptyMap = new Map();
const nonEmptyMap = new Map([['key', 'value']]);
console.log(emptyMap.size === 0); // true
console.log(nonEmptyMap.size === 0); // false
// More readable helper function
function isEmpty(map) {
return map.size === 0;
}
console.log(isEmpty(emptyMap)); // true
console.log(isEmpty(nonEmptyMap)); // false
20. How do you find a specific entry in a Map?
const users = new Map([
[1, { name: 'Alice', role: 'admin' }],
[2, { name: 'Bob', role: 'user' }],
[3, { name: 'Charlie', role: 'admin' }]
]);
// Find by key
const user = users.get(2);
console.log(user); // { name: 'Bob', role: 'user' }
// Find by value property
function findUserByName(usersMap, name) {
for (const [id, user] of usersMap) {
if (user.name === name) {
return { id, ...user };
}
}
return null;
}
console.log(findUserByName(users, 'Charlie')); // { id: 3, name: 'Charlie', role: 'admin' }
Advanced Level (Questions 21-30)
21. How do you implement a Map-based cache with expiration?
class ExpiringMap {
constructor() {
this.map = new Map();
this.timers = new Map();
}
set(key, value, ttl = 5000) {
// Clear existing timer
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
}
// Set value
this.map.set(key, value);
// Set expiration timer
const timer = setTimeout(() => {
this.delete(key);
}, ttl);
this.timers.set(key, timer);
return this;
}
get(key) {
return this.map.get(key);
}
delete(key) {
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
return this.map.delete(key);
}
has(key) {
return this.map.has(key);
}
get size() {
return this.map.size;
}
}
// Usage
const cache = new ExpiringMap();
cache.set('temp-data', 'This will expire', 2000);
console.log(cache.get('temp-data')); // 'This will expire'
setTimeout(() => {
console.log(cache.get('temp-data')); // undefined (expired)
}, 3000);
22. How do you group array elements using a Map?
const students = [
{ name: 'Alice', grade: 'A', subject: 'Math' },
{ name: 'Bob', grade: 'B', subject: 'Math' },
{ name: 'Charlie', grade: 'A', subject: 'Science' },
{ name: 'David', grade: 'B', subject: 'Math' },
{ name: 'Eva', grade: 'A', subject: 'Science' }
];
function groupBy(array, keyFn) {
const grouped = new Map();
for (const item of array) {
const key = keyFn(item);
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key).push(item);
}
return grouped;
}
const byGrade = groupBy(students, student => student.grade);
const bySubject = groupBy(students, student => student.subject);
console.log(byGrade.get('A')); // Students with grade A
console.log(bySubject.get('Math')); // Students studying Math
23. How do you implement a bidirectional Map?
class BiMap {
constructor(entries = []) {
this.keyToValue = new Map();
this.valueToKey = new Map();
for (const [key, value] of entries) {
this.set(key, value);
}
}
set(key, value) {
// Remove existing mappings
if (this.keyToValue.has(key)) {
const oldValue = this.keyToValue.get(key);
this.valueToKey.delete(oldValue);
}
if (this.valueToKey.has(value)) {
const oldKey = this.valueToKey.get(value);
this.keyToValue.delete(oldKey);
}
// Set new mappings
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
return this;
}
get(key) {
return this.keyToValue.get(key);
}
getByValue(value) {
return this.valueToKey.get(value);
}
delete(key) {
if (this.keyToValue.has(key)) {
const value = this.keyToValue.get(key);
this.keyToValue.delete(key);
this.valueToKey.delete(value);
return true;
}
return false;
}
get size() {
return this.keyToValue.size;
}
}
// Usage
const biMap = new BiMap([['en', 'English'], ['es', 'Spanish']]);
console.log(biMap.get('en')); // 'English'
console.log(biMap.getByValue('Spanish')); // 'es'
24. How do you use Map with complex key comparison?
// Using objects as keys requires exact reference matching
const obj1 = { x: 1, y: 2 };
const obj2 = { x: 1, y: 2 }; // Different object, same content
const map = new Map();
map.set(obj1, 'First point');
map.set(obj2, 'Second point');
console.log(map.size); // 2 (different references)
console.log(map.get(obj1)); // 'First point'
console.log(map.get({ x: 1, y: 2 })); // undefined (new object reference)
// Custom Map with content-based key comparison
class ContentMap {
constructor() {
this.map = new Map();
}
_getKey(obj) {
return JSON.stringify(obj);
}
set(key, value) {
const serializedKey = this._getKey(key);
this.map.set(serializedKey, { originalKey: key, value });
return this;
}
get(key) {
const serializedKey = this._getKey(key);
const entry = this.map.get(serializedKey);
return entry ? entry.value : undefined;
}
has(key) {
return this.map.has(this._getKey(key));
}
}
const contentMap = new ContentMap();
contentMap.set({ x: 1, y: 2 }, 'Point A');
console.log(contentMap.get({ x: 1, y: 2 })); // 'Point A' (works with content matching)
25. How do you implement Map serialization and deserialization?
class SerializableMap extends Map {
toJSON() {
return {
dataType: 'Map',
value: Array.from(this.entries())
};
}
static fromJSON(jsonStr) {
const data = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr;
if (data.dataType === 'Map') {
return new SerializableMap(data.value);
}
throw new Error('Invalid Map JSON format');
}
serialize() {
return JSON.stringify(this);
}
static deserialize(jsonStr) {
return SerializableMap.fromJSON(jsonStr);
}
}
// Usage
const originalMap = new SerializableMap([
['user1', { name: 'Alice', active: true }],
['user2', { name: 'Bob', active: false }]
]);
const serialized = originalMap.serialize();
console.log(serialized); // JSON string
const deserialized = SerializableMap.deserialize(serialized);
console.log(deserialized.get('user1')); // { name: 'Alice', active: true }
26. How do you create a Map-based LRU (Least Recently Used) cache?
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (this.cache.has(key)) {
// Move to end (most recently used)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return undefined;
}
set(key, value) {
if (this.cache.has(key)) {
// Update existing key
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// Remove least recently used (first entry)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
has(key) {
return this.cache.has(key);
}
get size() {
return this.cache.size;
}
clear() {
this.cache.clear();
}
}
// Usage
const lru = new LRUCache(3);
lru.set('a', 1);
lru.set('b', 2);
lru.set('c', 3);
lru.get('a'); // Makes 'a' most recently used
lru.set('d', 4); // Removes 'b' (least recently used)
console.log(lru.has('b')); // false
console.log(lru.has('a')); // true
27. How do you perform Map operations similar to array methods?
const scores = new Map([
['Alice', 95],
['Bob', 87],
['Charlie', 92],
['David', 78],
['Eva', 89]
]);
// Map equivalent of Array.filter()
function mapFilter(map, predicate) {
const filtered = new Map();
for (const [key, value] of map) {
if (predicate(value, key, map)) {
filtered.set(key, value);
}
}
return filtered;
}
// Map equivalent of Array.map()
function mapMap(map, transformer) {
const transformed = new Map();
for (const [key, value] of map) {
const newValue = transformer(value, key, map);
transformed.set(key, newValue);
}
return transformed;
}
// Map equivalent of Array.reduce()
function mapReduce(map, reducer, initialValue) {
let accumulator = initialValue;
for (const [key, value] of map) {
accumulator = reducer(accumulator, value, key, map);
}
return accumulator;
}
// Usage examples
const highScores = mapFilter(scores, score => score >= 90);
console.log(highScores); // Map with Alice, Charlie
const percentages = mapMap(scores, score => `${score}%`);
console.log(percentages); // Map with percentage strings
const totalScore = mapReduce(scores, (sum, score) => sum + score, 0);
console.log(totalScore); // 441
28. How do you handle Map operations with async/await?
async function fetchUserData(userId) {
// Simulate API call
return new Promise(resolve => {
setTimeout(() => {
resolve({ id: userId, name: `User${userId}`, active: true });
}, 100);
});
}
async function populateMapAsync(userIds) {
const userMap = new Map();
// Sequential processing
for (const id of userIds) {
const userData = await fetchUserData(id);
userMap.set(id, userData);
}
return userMap;
}
async function populateMapConcurrent(userIds) {
const userMap = new Map();
// Concurrent processing
const promises = userIds.map(id => fetchUserData(id));
const results = await Promise.all(promises);
results.forEach((userData, index) => {
userMap.set(userIds[index], userData);
});
return userMap;
}
// Usage
async function demo() {
const userIds = [1, 2, 3, 4, 5];
console.time('Sequential');
const map1 = await populateMapAsync(userIds);
console.timeEnd('Sequential'); // ~500ms
console.time('Concurrent');
const map2 = await populateMapConcurrent(userIds);
console.timeEnd('Concurrent'); // ~100ms
console.log(map1.size, map2.size); // Both should be 5
}
demo();
29. How do you implement a Map-based event system?
class EventMap {
constructor() {
this.events = new Map();
}
on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, new Set());
}
this.events.get(eventName).add(callback);
return this;
}
off(eventName, callback) {
if (this.events.has(eventName)) {
this.events.get(eventName).delete(callback);
// Clean up empty event sets
if (this.events.get(eventName).size === 0) {
this.events.delete(eventName);
}
}
return this;
}
emit(eventName, ...args) {
if (this.events.has(eventName)) {
this.events.get(eventName).forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error(`Error in event handler for ${eventName}:`, error);
}
});
}
return this;
}
once(eventName, callback) {
const onceWrapper = (...args) => {
callback(...args);
this.off(eventName, onceWrapper);
};
return this.on(eventName, onceWrapper);
}
listenerCount(eventName) {
return this.events.has(eventName) ? this.events.get(eventName).size : 0;
}
eventNames() {
return Array.from(this.events.keys());
}
}
// Usage
const events = new EventMap();
events.on('user:login', (user) => {
console.log(`User ${user.name} logged in`);
});
events.on('user:login', (user) => {
console.log(`Welcome back, ${user.name}!`);
});
events.once('app:ready', () => {
console.log('Application is ready!');
});
events.emit('user:login', { name: 'Alice' });
events.emit('app:ready');
events.emit('app:ready'); // Won't trigger (once only)
30. How do you implement deep comparison and cloning for Maps?
class DeepMap extends Map {
// Deep clone a Map
clone() {
const cloned = new DeepMap();
for (const [key, value] of this) {
const clonedKey = this._deepClone(key);
const clonedValue = this._deepClone(value);
cloned.set(clonedKey, clonedValue);
}
return cloned;
}
// Deep equality comparison
equals(other) {
if (!(other instanceof Map) || this.size !== other.size) {
return false;
}
for (const [key, value] of this) {
if (!other.has(key) || !this._deepEquals(value, other.get(key))) {
return false;
}
}
return true;
}
_deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(item => this._deepClone(item));
}
if (obj instanceof Map) {
const cloned = new Map();
for (const [key, value] of obj) {
cloned.set(this._deepClone(key), this._deepClone(value));
}
return cloned;
}
if (typeof obj === 'object') {
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = this._deepClone(obj[key]);
}
}
return cloned;
}
return obj;
}
_deepEquals(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (typeof a !== typeof b) return false;
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
return a.every((item, index) => this._deepEquals(item, b[index]));
}
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) return false;
for (const [key, value] of a) {
if (!b.has(key) || !this._deepEquals(value, b.get(key))) {
return false;
}
}
return true;
}
if (typeof a === 'object' && typeof b === 'object') {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key =>
keysB.includes(key) && this._deepEquals(a[key], b[key])
);
}
return false;
}
}
// Usage
const original = new DeepMap([
['user', { name: 'Alice', preferences: { theme: 'dark' } }],
['config', { timeout: 5000, retries: 3 }]
]);
const cloned = original.clone();
cloned.get('user').name = 'Bob'; // Modify clone
console.log(original.get('user').name); // 'Alice' (original unchanged)
console.log(cloned.get('user').name); // 'Bob'
const map2 = new DeepMap([
['user', { name: 'Alice', preferences: { theme: 'dark' } }],
['config', { timeout: 5000, retries: 3 }]
]);
console.log(original.equals(map2)); // true (deep equality)
Performance Tips and Best Practices
When to Use Maps vs Objects
Use Maps when:
- You need non-string keys
- Key-value pairs are frequently added/removed
- You need to maintain insertion order
- You need to iterate over entries frequently
- The number of key-value pairs is unknown until runtime
Use Objects when:
- You have a record with known string keys
- You need JSON serialization (objects serialize naturally)
- You're working with existing APIs that expect objects
- You need property access syntax (
obj.prop
vsmap.get('prop')
)
Performance Considerations
- Iteration: Maps are generally faster for frequent iterations
- Key Lookup: Similar performance for both, but Maps handle edge cases better
- Memory: Maps may use more memory due to their internal structure
- Size: Getting the size of a Map is O(1), while objects require O(n) calculation
Conclusion
JavaScript Maps are powerful data structures that provide significant advantages over plain objects in many scenarios. They offer better performance for frequent additions and deletions, support any type of key, maintain insertion order, and provide a cleaner API for key-value operations.
By working through these 30 questions, you should now have a solid understanding of Maps and be able to use them effectively in your JavaScript projects. Whether you're building caches, managing collections of data, or implementing complex data structures, Maps provide the flexibility and performance you need.
Remember to consider the specific requirements of your use case when choosing between Maps and objects. Both have their place in JavaScript development, and understanding when to use each will make you a more effective developer.
Key takeaways:
- Maps preserve insertion order and support any key type
- They provide better performance for frequent modifications
- Maps have a rich API with methods like
forEach
,keys()
,values()
, andentries()
- Advanced patterns like LRU caches and event systems can be elegantly implemented with Maps
- Always consider your specific use case when choosing between Maps and objects
Common Pitfalls and How to Avoid Them
1. Forgetting Maps are Reference-Based for Object Keys
// Wrong - creates new object references
const map = new Map();
map.set({ id: 1 }, 'user1');
console.log(map.get({ id: 1 })); // undefined
// Correct - reuse object references
const userKey = { id: 1 };
map.set(userKey, 'user1');
console.log(map.get(userKey)); // 'user1'
2. Not Handling Undefined vs Non-Existent Keys
const map = new Map([['key', undefined]]);
// Wrong - can't distinguish between undefined value and missing key
if (map.get('key')) { /* won't execute even though key exists */ }
// Correct - explicitly check for key existence
if (map.has('key')) { /* will execute */ }
3. Modifying Maps During Iteration
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
// Potentially problematic - modifying during iteration
for (const [key, value] of map) {
if (value > 1) {
map.delete(key); // Can cause unexpected behavior
}
}
// Better approach - collect keys first, then modify
const keysToDelete = [];
for (const [key, value] of map) {
if (value > 1) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => map.delete(key));
4. Assuming Maps are JSON Serializable
const map = new Map([['a', 1], ['b', 2]]);
// Wrong - results in empty object
console.log(JSON.stringify(map)); // "{}"
// Correct - convert to array first
console.log(JSON.stringify([...map])); // [["a",1],["b",2]]
// Or implement custom serialization
map.toJSON = function() {
return [...this];
};
console.log(JSON.stringify(map)); // [["a",1],["b",2]]
Real-World Use Cases
1. API Response Caching
class APICache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.timestamps = new Map();
this.ttl = ttl;
}
get(url) {
if (this.cache.has(url)) {
const timestamp = this.timestamps.get(url);
if (Date.now() - timestamp < this.ttl) {
return this.cache.get(url);
} else {
// Expired
this.cache.delete(url);
this.timestamps.delete(url);
}
}
return null;
}
set(url, data) {
this.cache.set(url, data);
this.timestamps.set(url, Date.now());
}
clear() {
this.cache.clear();
this.timestamps.clear();
}
}
// Usage
const apiCache = new APICache();
apiCache.set('/api/users', [{ id: 1, name: 'Alice' }]);
console.log(apiCache.get('/api/users')); // Returns cached data
2. State Management
class StateManager {
constructor(initialState = {}) {
this.state = new Map(Object.entries(initialState));
this.subscribers = new Map();
}
getState(key) {
return key ? this.state.get(key) : Object.fromEntries(this.state);
}
setState(key, value) {
const oldValue = this.state.get(key);
this.state.set(key, value);
// Notify subscribers
if (this.subscribers.has(key)) {
this.subscribers.get(key).forEach(callback => {
callback(value, oldValue);
});
}
}
subscribe(key, callback) {
if (!this.subscribers.has(key)) {
this.subscribers.set(key, new Set());
}
this.subscribers.get(key).add(callback);
// Return unsubscribe function
return () => {
this.subscribers.get(key).delete(callback);
if (this.subscribers.get(key).size === 0) {
this.subscribers.delete(key);
}
};
}
}
// Usage
const stateManager = new StateManager({ count: 0 });
const unsubscribe = stateManager.subscribe('count', (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});
stateManager.setState('count', 1); // Logs: Count changed from 0 to 1
3. Memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit!');
return cache.get(key);
}
console.log('Computing...');
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Usage
const fibonacci = memoize((n) => {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(10)); // Computes and caches intermediate results
console.log(fibonacci(10)); // Cache hit!
4. Dependency Tracking
class DependencyTracker {
constructor() {
this.dependencies = new Map();
this.dependents = new Map();
}
addDependency(dependent, dependency) {
// Track what this dependent depends on
if (!this.dependencies.has(dependent)) {
this.dependencies.set(dependent, new Set());
}
this.dependencies.get(dependent).add(dependency);
// Track what depends on this dependency
if (!this.dependents.has(dependency)) {
this.dependents.set(dependency, new Set());
}
this.dependents.get(dependency).add(dependent);
}
getDependencies(item) {
return Array.from(this.dependencies.get(item) || []);
}
getDependents(item) {
return Array.from(this.dependents.get(item) || []);
}
removeDependency(dependent, dependency) {
if (this.dependencies.has(dependent)) {
this.dependencies.get(dependent).delete(dependency);
}
if (this.dependents.has(dependency)) {
this.dependents.get(dependency).delete(dependent);
}
}
// Find circular dependencies
hasCircularDependency(item, visited = new Set()) {
if (visited.has(item)) {
return true;
}
visited.add(item);
const dependencies = this.dependencies.get(item) || new Set();
for (const dep of dependencies) {
if (this.hasCircularDependency(dep, new Set(visited))) {
return true;
}
}
return false;
}
}
// Usage
const tracker = new DependencyTracker();
tracker.addDependency('moduleA', 'moduleB');
tracker.addDependency('moduleB', 'moduleC');
tracker.addDependency('moduleC', 'moduleA'); // Creates circular dependency
console.log(tracker.hasCircularDependency('moduleA')); // true
Testing Maps: Best Practices
1. Unit Testing Map Operations
// Example using Jest
describe('Map Operations', () => {
let testMap;
beforeEach(() => {
testMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
});
test('should add new key-value pair', () => {
testMap.set('key3', 'value3');
expect(testMap.has('key3')).toBe(true);
expect(testMap.get('key3')).toBe('value3');
expect(testMap.size).toBe(3);
});
test('should update existing key', () => {
testMap.set('key1', 'newValue');
expect(testMap.get('key1')).toBe('newValue');
expect(testMap.size).toBe(2); // Size shouldn't change
});
test('should delete key-value pair', () => {
const deleted = testMap.delete('key1');
expect(deleted).toBe(true);
expect(testMap.has('key1')).toBe(false);
expect(testMap.size).toBe(1);
});
test('should handle non-existent keys gracefully', () => {
expect(testMap.get('nonExistent')).toBeUndefined();
expect(testMap.has('nonExistent')).toBe(false);
expect(testMap.delete('nonExistent')).toBe(false);
});
});
2. Testing Custom Map Classes
describe('LRUCache', () => {
test('should evict least recently used item', () => {
const cache = new LRUCache(2);
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3); // Should evict 'a'
expect(cache.has('a')).toBe(false);
expect(cache.has('b')).toBe(true);
expect(cache.has('c')).toBe(true);
});
test('should update LRU order on access', () => {
const cache = new LRUCache(2);
cache.set('a', 1);
cache.set('b', 2);
cache.get('a'); // Makes 'a' most recently used
cache.set('c', 3); // Should evict 'b' instead of 'a'
expect(cache.has('a')).toBe(true);
expect(cache.has('b')).toBe(false);
expect(cache.has('c')).toBe(true);
});
});
Browser Compatibility and Polyfills
Maps are supported in all modern browsers, but if you need to support older environments:
IE11 Support
// Simple Map polyfill for older browsers
if (!window.Map) {
window.Map = function() {
this._keys = [];
this._values = [];
this.size = 0;
};
Map.prototype.set = function(key, value) {
const index = this._keys.indexOf(key);
if (index === -1) {
this._keys.push(key);
this._values.push(value);
this.size++;
} else {
this._values[index] = value;
}
return this;
};
Map.prototype.get = function(key) {
const index = this._keys.indexOf(key);
return index === -1 ? undefined : this._values[index];
};
Map.prototype.has = function(key) {
return this._keys.indexOf(key) !== -1;
};
Map.prototype.delete = function(key) {
const index = this._keys.indexOf(key);
if (index !== -1) {
this._keys.splice(index, 1);
this._values.splice(index, 1);
this.size--;
return true;
}
return false;
};
Map.prototype.clear = function() {
this._keys = [];
this._values = [];
this.size = 0;
};
}
Conclusion and Next Steps
Mastering JavaScript Maps opens up new possibilities for efficient data management in your applications. From simple key-value storage to complex caching mechanisms and state management systems, Maps provide the foundation for many advanced programming patterns.
What's Next?
- Explore WeakMaps: Learn about WeakMaps for garbage collection-friendly key-value storage
- Study Set Objects: Understand Sets, which are similar to Maps but store unique values only
- Advanced Patterns: Investigate more complex data structures like Tries, Graphs, and Trees using Maps
- Performance Analysis: Benchmark Maps vs Objects in your specific use cases
- Framework Integration: Learn how Maps integrate with popular frameworks like React, Vue, and Angular
Practice Exercises
Try implementing these additional Map-based projects:
- URL Router: Create a routing system using Maps for path matching
- Template Engine: Build a simple template engine with Map-based variable storage
- Database ORM: Design a lightweight ORM using Maps for entity management
- Game State Manager: Implement a game state system with Maps for player data
- Configuration Manager: Create a hierarchical configuration system
Remember, the key to mastering Maps is practice. Start with simple use cases and gradually work your way up to more complex implementations. Each project will deepen your understanding and reveal new possibilities for using Maps in your JavaScript applications.
Happy coding!
Top comments (1)
Thanks for sharing. Although I rarely use Map and Set, I still find them very useful.