AlgoInsight

Free lesson · JavaScript Basic to Advance

Map and Set

Easy5 min read

Beyond Objects and Arrays

JavaScript's built-in collection types — objects and arrays — handle most data structuring needs. But they have limitations. Objects can only use string and symbol keys. Arrays don't enforce uniqueness or fast lookup. Both behave awkwardly for certain common patterns.

Map and Set are modern collection types that fill these gaps. They're not replacements for objects and arrays — they're specialized tools for specific cases.

// Map: key-value pairs with any key type
const scores = new Map();
scores.set("alice", 85);
scores.set("bob", 72);
scores.set(42, "the answer");

console.log(scores.get("alice"));
console.log(scores.size);

// Set: unique values
const tags = new Set();
tags.add("javascript");
tags.add("modern");
tags.add("javascript");   // duplicate, ignored

console.log(tags.size);
console.log(tags.has("modern"));

This Topic covers both. They're not as commonly used as objects and arrays, but knowing when to reach for them is a useful skill — and they come up in JavaScript interviews.

When Objects Aren't Enough

Objects are JavaScript's default key-value structure, but they have constraints:

Keys must be strings or symbols. If you try to use a number, it gets converted to a string. If you try to use an object, the object gets converted to its [object Object] string representation (and every object becomes the same key).

const obj = {};
obj[1] = "one";
obj["1"] = "string one";
console.log(obj);   // { '1': 'string one' } — only one key, last write wins

obj[{ a: 1 }] = "first";
obj[{ b: 2 }] = "second";
console.log(obj);   // both objects convert to "[object Object]" — collision

Objects have inherited properties. Properties on Object.prototype (toString, hasOwnProperty, etc.) appear when iterating, and there's a small risk of name collisions. We covered this in Topic 7.4.

No simple size operation. To count properties on an object, you need Object.keys(obj).length.

Insertion order is mostly preserved, but with integer keys sorted numerically first. This is well-specified in modern engines but can be surprising.

For most cases, objects are fine. But if you need any of: non-string keys, predictable iteration order regardless of key type, or frequent additions and removals, Map is the cleaner choice.

Interactive animation: Object keys that never collide - in a Map, two object keys stay two separate entries, compared by reference.

Map: Any Keys, Predictable Behavior

A Map is an ordered collection of key-value pairs. Keys can be of any type — strings, numbers, objects, even other Maps:

const userCache = new Map();

const user1 = { id: 1, name: "Olena" };
const user2 = { id: 2, name: "Nikhil" };

// Use objects as keys
userCache.set(user1, { lastSeen: Date.now() });
userCache.set(user2, { lastSeen: Date.now() - 1000 });

console.log(userCache.get(user1));
console.log(userCache.has(user1));
console.log(userCache.size);

// Numeric keys stay numeric
const indexMap = new Map();
indexMap.set(1, "one");
indexMap.set("1", "string one");
console.log(indexMap.get(1));     // "one"
console.log(indexMap.get("1"));   // "string one" — distinct from numeric 1

The Map API is method-based, not bracket-based:

Iteration is straightforward — Maps preserve insertion order regardless of key type:

const m = new Map();
m.set("c", 3);
m.set(1, "numeric one");
m.set("a", 1);
m.set("b", 2);

// for...of iterates entries in insertion order
for (const [key, value] of m) {
  console.log(key, value);
}
// "c" 3
// 1 "numeric one"
// "a" 1
// "b" 2

Compare with an object, where integer-like keys come first regardless of insertion order. Maps are predictable — what you put in is the order you get out.

Interactive animation: Insertion order, guaranteed - a Map with numeric keys iterates in exactly the order the entries went in.

When to Use Map vs Object

A practical decision guide:

Use Map when:

Use Object when:

Most JavaScript code uses objects. Maps are a specialized tool. But for caches, lookup tables, frequency counts (counting occurrences in interview problems), and any scenario with non-string keys — Map is the right choice.

Set: Just Values, No Duplicates

A Set is a collection of unique values. You add values; if you try to add a duplicate, nothing happens.

const visited = new Set();

visited.add("homepage");
visited.add("login");
visited.add("dashboard");
visited.add("homepage");   // duplicate, ignored

console.log(visited.size);   // 3
console.log(visited.has("login"));     // true
console.log(visited.has("settings"));  // false

// Convert to array if needed
console.log([...visited]);

The Set API:

The killer use case for Set: deduplication.

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

Pass an array into new Set(...) and the Set drops duplicates. Spreading the Set back into an array gives you a deduplicated array. This is the standard JavaScript idiom for "remove duplicates from an array."

Another common pattern: tracking what you've seen during iteration.

function findFirstDuplicate(arr) {
  const seen = new Set();
  for (const item of arr) {
    if (seen.has(item)) return item;
    seen.add(item);
  }
  return null;
}

console.log(findFirstDuplicate([1, 2, 3, 4, 2, 5]));   // 2
console.log(findFirstDuplicate([1, 2, 3, 4, 5]));       // null

The seen set tracks every item we've encountered. The first repeat triggers the return. This is a classic interview pattern — finding duplicates, finding intersections, finding unique elements.

Interactive animation: Dedupe in one line, membership in O(1) - the Set ignores duplicates and has() answers membership.

Equality in Map and Set

Both Map and Set use a comparison rule called SameValueZero for keys/values. It's like === but treats NaN as equal to itself.

const set = new Set();
set.add(NaN);
console.log(set.has(NaN));   // true — NaN is "equal" to itself in Sets

const obj1 = { a: 1 };
const obj2 = { a: 1 };

const map = new Map();
map.set(obj1, "first");
console.log(map.get(obj1));   // "first"
console.log(map.get(obj2));   // undefined — different reference, even if contents match

For object keys/values, comparison is by reference, not by content. Two separately-created objects with identical contents are different keys. This matches normal JavaScript semantics (Topic 7.1) — === on objects compares references.

graph TD
    A[Map and Set] --> B[Map: key-value pairs]
    A --> C[Set: unique values]

    B --> D[Any key type]
    B --> E[Predictable order]
    B --> F[Real size property]

    C --> G[Auto-deduplicates]
    C --> H[Fast membership check]
    C --> I[Convert to array with spread]

    style A fill:#7c3aed,color:#fff
This diagram summarizes Map and Set. Map is a collection of key-value pairs where keys can be any type, iteration order is predictable, and you have a real size property. Set is a collection of unique values that automatically drops duplicates, supports fast membership checks, and converts back to an array via spread. Both use reference equality for object keys and values, with the special rule that NaN is considered equal to itself.

WeakMap and WeakSet

Two related types exist that aren't widely used but are worth knowing about. WeakMap and WeakSet hold references that don't prevent garbage collection. If the only reference to a key is in a WeakMap, the entry can be cleaned up automatically.

const cache = new WeakMap();

function getMetadata(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const meta = { id: Math.random(), createdAt: Date.now() };
  cache.set(obj, meta);
  return meta;
}

let user = { name: "Olena" };
console.log(getMetadata(user));

user = null;   // original object can now be GC'd, and its cache entry too

The use case: associating metadata with objects without keeping them alive. Real Map entries prevent garbage collection of their keys; WeakMap entries don't. WeakMap and WeakSet have a limited API (no iteration, no size) precisely because the contents can change as garbage collection happens.

You'll rarely use these directly. But knowing they exist matters for senior interviews and for understanding memory-aware library code.

Common Pitfalls

Warning: The Map/Set traps.

Forgetting .get and .set syntax. Maps don't support bracket access — map["key"] doesn't work the way it does on objects. Use .get() and .set().

Trying to JSON.stringify a Map. Maps don't serialize cleanly. JSON.stringify(new Map([["a", 1]])) produces "{}". To serialize, convert to entries first: JSON.stringify([...map]).

Using objects as keys without understanding reference equality. Two { a: 1 } literals are different objects and different keys. To use objects as Map keys consistently, hold a stable reference.

Reaching for Map when Object would do. For known-string-key data with a fixed schema, objects are simpler and integrate better with JSON, React, and most frameworks. Map is a specialized tool.

Why This Matters in Interviews

Interview: Map and Set come up in coding problems all the time. "Find duplicates in an array" → Set. "Count occurrences" → Map. "Cache results by argument" → Map. Senior interviewers expect you to reach for these naturally when the problem fits.

Conceptual questions: "When would you use Map instead of Object?" The expected answer covers non-string keys, predictable iteration order, and frequent mutation.

Topic 10.7 — Interview Lens: Modern JavaScript Patterns — covers the deeper treatment.

Try It

Exercise: Solve each problem with a Map or Set:

  1. Given an array of strings, return a Map of word → count showing how many times each word appears
  2. Given an array of user objects, return a Set of unique role names that appear
  3. Given two arrays, return a new array containing only the values that appear in both (intersection)
  4. Write a memoize function using Map: memoize(fn) returns a function that caches results by argument

Each one is 5-10 lines. By the time you can solve them fluently, Map and Set are part of your toolkit alongside objects and arrays.

Back to JavaScript Basic to Advance