Asher Cohen
Back to posts

JavaScript Map and Set Reference

Complete guide to JavaScript Map and Set data structures — methods, properties, and practical usage patterns

Introduction

Map and Set are built-in JavaScript data structures introduced in ES6 that provide powerful alternatives to plain objects for specific use cases. Understanding when and how to use them is essential for writing efficient, clean code.

Map

A Map is a collection of key-value pairs where keys can be any type (objects, functions, primitives).

Creating a Map

// Empty Map
const map = new Map();

// From array of key-value pairs
const mapFromArray = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  [42, 'the answer']
]);

// From another Map
const mapCopy = new Map(mapFromArray);

// Map with object keys
const userKey = { id: 1 };
const userMap = new Map([[userKey, { name: 'John' }]]);

Instance Methods

set(key, value)

Purpose: Adds or updates a key-value pair.

Returns: The Map instance (chainable)

const map = new Map();

// Add new key-value pairs
map.set('name', 'Alice');
map.set('age', 30);

// Chain multiple set calls
map
  .set('city', 'Paris')
  .set('country', 'France')
  .set('language', 'French');

// Object keys
const obj = { id: 1 };
map.set(obj, 'value for object key');

// Function keys
function myFunc() {}
map.set(myFunc, 'value for function key');

get(key)

Purpose: Retrieves the value for a given key.

Returns: The value associated with the key, or undefined if not found

const map = new Map([['name', 'Bob'], ['age', 25]]);

console.log(map.get('name')); // 'Bob'
console.log(map.get('age')); // 25
console.log(map.get('nonexistent')); // undefined

// Object keys must be the same reference
const key = { id: 1 };
map.set(key, 'value');
console.log(map.get(key)); // 'value'
console.log(map.get({ id: 1 })); // undefined (different object)

has(key)

Purpose: Checks if a key exists in the Map.

Returns: Boolean

const map = new Map([['active', true]]);

console.log(map.has('active')); // true
console.log(map.has('inactive')); // false

// Check before getting
if (map.has('user')) {
  const user = map.get('user');
  // Process user
}

delete(key)

Purpose: Removes a key-value pair by key.

Returns: Boolean (true if key existed, false otherwise)

const map = new Map([['temp', 'data'], ['permanent', 'data']]);

console.log(map.delete('temp')); // true
console.log(map.delete('nonexistent')); // false
console.log(map.has('temp')); // false

// Conditional deletion
if (map.has('cache')) {
  map.delete('cache');
}

clear()

Purpose: Removes all key-value pairs from the Map.

Returns: undefined

const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

console.log(map.size); // 3
map.clear();
console.log(map.size); // 0
console.log(map.has('a')); // false

forEach(callback)

Purpose: Iterates through the Map in insertion order.

Callback Parameters: (value, key, map)

Returns: undefined

const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

map.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});
// Output:
// a: 1
// b: 2
// c: 3

// With thisArg
const context = { multiplier: 10 };
map.forEach(function(value, key) {
  console.log(`${key}: ${value * this.multiplier}`);
}, context);

Properties

size

Purpose: Returns the number of key-value pairs.

Returns: Number

const map = new Map();
console.log(map.size); // 0

map.set('a', 1).set('b', 2);
console.log(map.size); // 2

map.delete('a');
console.log(map.size); // 1

Iteration

Map is iterable and provides multiple iteration methods.

entries()

Purpose: Returns an iterator of [key, value] pairs.

Returns: MapIterator

const map = new Map([
  ['name', 'Charlie'],
  ['age', 35]
]);

for (const [key, value] of map.entries()) {
  console.log(`${key}: ${value}`);
}

// entries() is the default iterator
for (const [key, value] of map) {
  console.log(`${key}: ${value}`);
}

keys()

Purpose: Returns an iterator of keys.

Returns: MapIterator

const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

for (const key of map.keys()) {
  console.log(key); // 'a', 'b', 'c'
}

// Convert to array
const keys = [...map.keys()]; // ['a', 'b', 'c']

values()

Purpose: Returns an iterator of values.

Returns: MapIterator

const map = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

for (const value of map.values()) {
  console.log(value); // 1, 2, 3
}

// Convert to array
const values = [...map.values()]; // [1, 2, 3]

Practical Map Patterns

Object Caching with WeakMap

// Cache expensive computations
const cache = new Map();

const expensiveOperation = (input) => {
  if (cache.has(input)) {
    return cache.get(input);
  }
  
  const result = computeExpensively(input);
  cache.set(input, result);
  return result;
};

Counting Occurrences

const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const wordCount = new Map();
words.forEach(word => {
  wordCount.set(word, (wordCount.get(word) || 0) + 1);
});

console.log(wordCount.get('apple')); // 3
console.log(wordCount.get('banana')); // 2

Grouping Data

const users = [
  { name: 'Alice', city: 'Paris' },
  { name: 'Bob', city: 'London' },
  { name: 'Charlie', city: 'Paris' }
];

const usersByCity = new Map();
users.forEach(user => {
  const cityUsers = usersByCity.get(user.city) || [];
  cityUsers.push(user);
  usersByCity.set(user.city, cityUsers);
});

console.log(usersByCity.get('Paris')); 
// [{ name: 'Alice', city: 'Paris' }, { name: 'Charlie', city: 'Paris' }]

Map vs Object

// Map advantages over plain objects:

// 1. Any type of key
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, 'value'); // Works!

const obj = {};
obj[objKey] = 'value'; // Key converted to string "[object Object]"

// 2. Size property
console.log(map.size); // O(1) - instant
console.log(Object.keys(obj).length); // O(n) - must count

// 3. Better performance for frequent additions/removals
// Map is optimized for these operations

// 4. Iterable by default
for (const [key, value] of map) {
  // Works out of the box
}

// 5. No prototype chain issues
map.set('toString', 'custom value'); // Safe
obj.toString = 'custom value'; // Breaks Object.prototype.toString

Set

A Set is a collection of unique values of any type.

Creating a Set

// Empty Set
const set = new Set();

// From array
const setFromArray = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(setFromArray.size); // 5 (duplicates removed)

// From string
const uniqueChars = new Set('hello');
console.log(uniqueChars.size); // 4 ('h', 'e', 'l', 'o')

// From another Set
const setCopy = new Set(setFromArray);

Instance Methods

add(value)

Purpose: Adds a value to the Set.

Returns: The Set instance (chainable)

const set = new Set();

// Add values
set.add(1);
set.add(2);
set.add(3);

// Chain add calls
set.add(4).add(5).add(6);

// Duplicate values are ignored
set.add(1);
set.add(1);
console.log(set.size); // 6 (not 8)

// Any type
set.add('string');
set.add({ key: 'value' });
set.add(function() {});

has(value)

Purpose: Checks if a value exists in the Set.

Returns: Boolean

const set = new Set(['apple', 'banana', 'orange']);

console.log(set.has('apple')); // true
console.log(set.has('grape')); // false

// Object references
const obj = { id: 1 };
set.add(obj);
console.log(set.has(obj)); // true
console.log(set.has({ id: 1 })); // false (different object)

delete(value)

Purpose: Removes a value from the Set.

Returns: Boolean (true if value existed, false otherwise)

const set = new Set(['a', 'b', 'c']);

console.log(set.delete('a')); // true
console.log(set.delete('a')); // false (already deleted)
console.log(set.has('a')); // false

clear()

Purpose: Removes all values from the Set.

Returns: undefined

const set = new Set([1, 2, 3]);
console.log(set.size); // 3

set.clear();
console.log(set.size); // 0

forEach(callback)

Purpose: Iterates through the Set in insertion order.

Callback Parameters: (value, value, set) - value appears twice for compatibility with Map

Returns: undefined

const set = new Set(['a', 'b', 'c']);

set.forEach((value) => {
  console.log(value); // 'a', 'b', 'c'
});

// The second parameter is the same as the first
set.forEach((value1, value2) => {
  console.log(value1 === value2); // true
});

Properties

size

Purpose: Returns the number of values in the Set.

Returns: Number

const set = new Set();
console.log(set.size); // 0

set.add(1).add(2).add(3);
console.log(set.size); // 3

set.delete(1);
console.log(set.size); // 2

Iteration

Set is iterable and provides multiple iteration methods.

entries()

Purpose: Returns an iterator of [value, value] pairs.

Returns: SetIterator

const set = new Set(['a', 'b', 'c']);

for (const [value1, value2] of set.entries()) {
  console.log(value1 === value2); // true
  console.log(value1); // 'a', 'b', 'c'
}

// entries() is the default iterator
for (const entry of set) {
  console.log(entry); // ['a', 'a'], ['b', 'b'], ['c', 'c']
}

keys() and values()

Purpose: Both return an iterator of values (identical in Set).

Returns: SetIterator

const set = new Set([1, 2, 3]);

for (const value of set.keys()) {
  console.log(value); // 1, 2, 3
}

for (const value of set.values()) {
  console.log(value); // 1, 2, 3
}

// Convert to array
const values = [...set.values()]; // [1, 2, 3]
const keys = [...set.keys()]; // [1, 2, 3] (same as values)

Practical Set Patterns

Remove Duplicates from Array

const arrayWithDuplicates = [1, 2, 2, 3, 3, 4, 5, 5];

const uniqueArray = [...new Set(arrayWithDuplicates)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]

// For objects, use a Map or custom comparison
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' }
];

const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];

Set Operations

const setA = new Set([1, 2, 3, 4, 5]);
const setB = new Set([4, 5, 6, 7, 8]);

// Union: all elements from both sets
const union = new Set([...setA, ...setB]);
console.log(union); // {1, 2, 3, 4, 5, 6, 7, 8}

// Intersection: elements in both sets
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log(intersection); // {4, 5}

// Difference: elements in A but not in B
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log(difference); // {1, 2, 3}

// Symmetric Difference: elements in A or B but not both
const symmetricDifference = new Set(
  [...setA].filter(x => !setB.has(x)).concat([...setB].filter(x => !setA.has(x)))
);
console.log(symmetricDifference); // {1, 2, 3, 6, 7, 8}

Tracking Visited Items

// BFS/DFS traversal
function traverse(node) {
  const visited = new Set();
  const queue = [node];
  
  while (queue.length > 0) {
    const current = queue.shift();
    
    if (visited.has(current)) continue;
    
    visited.add(current);
    // Process current node
    
    queue.push(...current.children);
  }
}

// Track user interactions
const clickedButtons = new Set();

button.addEventListener('click', () => {
  if (clickedButtons.has(button.id)) return;
  clickedButtons.add(button.id);
  // Handle click
});

Filtering Unique Values

// Filter array to keep only unique values
const items = ['a', 'b', 'a', 'c', 'b'];
const unique = [...new Set(items)]; // ['a', 'b', 'c']

// Filter based on property
const products = [
  { id: 1, category: 'electronics' },
  { id: 2, category: 'books' },
  { id: 3, category: 'electronics' }
];

const uniqueCategories = [...new Set(products.map(p => p.category))];
console.log(uniqueCategories); // ['electronics', 'books']

WeakSet

A WeakSet is a collection of unique objects with weak references.

const weakSet = new WeakSet();

const obj1 = { id: 1 };
const obj2 = { id: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true

// Only objects allowed
// weakSet.add('string'); // TypeError

// No iteration methods (no keys(), values(), entries())
// No size property
// No clear() method

// Weak reference: objects can be garbage collected
obj1 = null; // obj1 can now be garbage collected from weakSet

Use Cases:

  • Tracking objects without preventing garbage collection
  • Marking objects as processed
  • Preventing re-processing of DOM nodes

Performance Comparison

// Map vs Object for frequent additions/deletions
const map = new Map();
const obj = {};

// Map is faster for:
// - Frequent key additions/deletions
// - Large datasets
// - Non-string keys

// Object is faster for:
// - Simple key-value storage
// - JSON serialization
// - When you need prototype methods

// Set vs Array for existence checks
const array = [1, 2, 3, 4, 5];
const set = new Set([1, 2, 3, 4, 5]);

// O(n) - must iterate through array
array.includes(5);

// O(1) - hash table lookup
set.has(5); // Much faster for large collections

Summary Table

Map Methods

MethodPurposeReturns
set(key, value)Add/update key-valueMap (chainable)
get(key)Retrieve valueValue or undefined
has(key)Check key existsBoolean
delete(key)Remove by keyBoolean
clear()Remove allundefined
forEach(cb)Iterateundefined
entries()[key, value] iteratorMapIterator
keys()Keys iteratorMapIterator
values()Values iteratorMapIterator
sizeCount (property)Number

Set Methods

MethodPurposeReturns
add(value)Add valueSet (chainable)
has(value)Check value existsBoolean
delete(value)Remove valueBoolean
clear()Remove allundefined
forEach(cb)Iterateundefined
entries()[value, value] iteratorSetIterator
keys()Values iteratorSetIterator
values()Values iteratorSetIterator
sizeCount (property)Number

Conclusion

Map and Set are powerful additions to JavaScript:

Use Map when:

  • You need non-string keys (objects, functions)
  • You need to maintain insertion order
  • You need frequent additions/removals
  • You need size property (O(1) vs O(n))

Use Set when:

  • You need unique values only
  • You need fast existence checks (O(1) vs O(n))
  • You need set operations (union, intersection, difference)
  • You need to remove duplicates from arrays

Both provide better performance and cleaner APIs than plain objects or arrays for their specific use cases.

#javascript #data-structures #map #set #reference #web-development