Intermediate0 questionsFull Guide

JavaScript Map & Set Interview Questions

Map and Set are modern alternatives to objects and arrays with key advantages. Learn when and why to use them in interviews.

The Mental Model

Picture a museum with two rooms. The first room has labeled display cases — each case has a unique label and something inside it. You can look up any case by its exact label, add new cases, remove specific ones, or walk through all of them in the order they were added. The label can be anything — a word, a number, even a sculpture from another room. That's a Map: an ordered collection of key-value pairs where keys can be any type. The second room has a collection of unique sculptures with no labels — each sculpture just exists, and no two are the same. Adding the same sculpture twice just results in it being there once. Walking through the room gives you every sculpture in the order it arrived. That's a Set: an ordered collection of unique values. The key insight: objects and arrays already let you store key-value pairs and collections — so why do Map and Set exist? Objects coerce all keys to strings. Arrays allow duplicates and have no fast "does this exist?" lookup. Map solves the string-coercion problem and gives you constant-time lookup by any key. Set solves the uniqueness and fast-membership-check problem that arrays can't do without linear search. They're not replacements — they're the right tool for specific jobs that objects and arrays handle poorly.

The Explanation

Map — the key-value store that takes any key type

// Creating a Map
const map = new Map()

// Setting entries — key can be ANY value
map.set('string key', 'value 1')
map.set(42, 'value 2')
map.set(true, 'value 3')

const objKey = { id: 1 }
const fnKey  = () => {}
map.set(objKey, 'value for object key')
map.set(fnKey,  'value for function key')
map.set(null, 'null is a valid key')
map.set(NaN,  'NaN is a valid key')

// Initialize with entries
const map2 = new Map([
  ['name', 'Alice'],
  ['age', 30],
  ['role', 'admin'],
])

// Core operations — all O(1)
map2.get('name')       // 'Alice'
map2.get('missing')    // undefined — not an error
map2.has('age')        // true
map2.has('missing')    // false
map2.size              // 3 — not .length like arrays
map2.set('name', 'Bob')  // overwrite existing key
map2.delete('role')    // true — returns boolean
map2.clear()           // removes all entries

Map vs Object — the critical differences

// Key types
const obj = {}
obj[1]       = 'number key'
obj[[1,2,3]] = 'array key'
obj[{ a:1 }] = 'object key'

Object.keys(obj)  // ['1', '1,2,3', '[object Object]']
// ALL KEYS ARE STRINGS — numbers, arrays, objects all get .toString() called

const map = new Map()
map.set(1, 'number key')
map.set([1,2,3], 'this specific array')  // the reference is the key
map.set({ a: 1 }, 'this specific object')

// ★ Object keys: only strings and Symbols
// ★ Map keys: any value, identity preserved, no coercion

// Prototype pollution risk
const obj = {}
'toString' in obj    // true — inherited from Object.prototype!
'hasOwnProperty' in obj  // true — dangerous with user-controlled keys

const map = new Map()
map.has('toString')  // false — clean, no inherited properties

// Iteration
const obj2 = { b: 2, a: 1, 2: 'two', 1: 'one' }
Object.keys(obj2)  // ['1', '2', 'b', 'a'] — integers first, then insertion order

const map2 = new Map([['b', 2], ['a', 1], [2, 'two'], [1, 'one']])
[...map2.keys()]   // ['b', 'a', 2, 1] — strict insertion order, always

// Size
Object.keys(obj).length  // O(n) — must enumerate all keys
map.size                 // O(1) — maintained automatically

// Performance
// Map is optimized for frequent additions and removals
// Object is slightly faster for static key sets the engine can optimize with hidden classes

Map iteration — all the ways

const scores = new Map([
  ['Alice', 95],
  ['Bob', 87],
  ['Carol', 91],
])

// for...of — most common
for (const [name, score] of scores) {
  console.log(`${name}: ${score}`)
}

// Explicit iterators
for (const key   of scores.keys())    { console.log(key) }
for (const value of scores.values())  { console.log(value) }
for (const entry of scores.entries()) { console.log(entry) } // [key, value] pairs

// Convert to array for array methods
const names   = [...scores.keys()]
const allScores = [...scores.values()]
const pairs   = [...scores.entries()]

// forEach
scores.forEach((value, key) => {   // note: value first, then key (opposite of entries)
  console.log(`${key} → ${value}`)
})

// Convert between Map and Object
const obj  = Object.fromEntries(scores)       // Map → Object
const map2 = new Map(Object.entries(obj))     // Object → Map

Set — unique values, fast membership testing

// Creating a Set
const set = new Set()
set.add(1)
set.add(2)
set.add(2)  // duplicate — ignored
set.add(3)
set.size    // 3 — not 4

// Initialize with any iterable
const fromArray = new Set([1, 2, 2, 3, 3, 3])  // Set {1, 2, 3}
const fromStr   = new Set('hello')              // Set {'h', 'e', 'l', 'o'}

// Core operations — all O(1)
set.has(2)    // true  — O(1) vs array's O(n)
set.has(99)   // false
set.add(4)    // returns the Set (chainable)
set.delete(1) // true — returns boolean
set.size      // property, not method
set.clear()   // removes all

// Uniqueness uses SameValueZero (like ===, except NaN === NaN)
const s = new Set([1, '1', NaN, NaN, 0, -0])
s.size  // 4 — 1 and '1' are different, NaN is deduplicated, 0 and -0 are same

// Objects: identity-based uniqueness (same reference = same, different object = different)
const s2 = new Set()
const obj1 = { x: 1 }
const obj2 = { x: 1 }
s2.add(obj1); s2.add(obj1)  // size: 1 — same reference
s2.add(obj2)                // size: 2 — different reference, even though "equal" content

Set operations — union, intersection, difference

const a = new Set([1, 2, 3, 4])
const b = new Set([3, 4, 5, 6])

// Union — everything in either set
const union = new Set([...a, ...b])               // {1, 2, 3, 4, 5, 6}

// Intersection — only in both
const intersection = new Set([...a].filter(x => b.has(x)))  // {3, 4}

// Difference — in a but not b
const difference = new Set([...a].filter(x => !b.has(x)))   // {1, 2}

// Symmetric difference — in one but not both
const symDiff = new Set([
  ...[...a].filter(x => !b.has(x)),
  ...[...b].filter(x => !a.has(x)),
])  // {1, 2, 5, 6}

// Subset check
const isSubset = (a, b) => [...a].every(x => b.has(x))
isSubset(new Set([3, 4]), a)  // true — {3,4} is subset of {1,2,3,4}

// ES2024 native set methods (now widely available)
a.union(b)           // native Set union
a.intersection(b)    // native Set intersection
a.difference(b)      // native Set difference
a.isSubsetOf(b)      // native subset check

The most important use case: O(1) deduplication and lookups

// Deduplicate an array — the canonical Set use
const raw     = [1, 2, 2, 3, 3, 3, 4]
const unique  = [...new Set(raw)]  // [1, 2, 3, 4]
// O(n) time, O(n) space, single line

// Fast membership testing — Set vs Array
const validRoles = new Set(['admin', 'editor', 'viewer'])

// Array — O(n) per check
['admin', 'editor', 'viewer'].includes(role)

// Set — O(1) per check
validRoles.has(role)

// For large collections or repeated lookups, this is a significant difference:
const millionItems = new Set(Array.from({ length: 1_000_000 }, (_, i) => i))
millionItems.has(999999)  // O(1) — instant
// array equivalent: millionItems.indexOf(999999) — O(n), scans up to 1M items

// Convert frequently-queried arrays to Sets
const blockedUsers = new Set(await fetchBlockedUserIds())
// Now every isBlocked(userId) check is O(1)

WeakMap and WeakSet — the garbage-collection-friendly versions

// WeakMap: keys must be objects, held weakly (no memory leak)
const cache = new WeakMap()

function process(element) {
  if (cache.has(element)) return cache.get(element)
  const result = expensiveOperation(element)
  cache.set(element, result)
  return result
}
// When 'element' is removed from the DOM and has no other references,
// the WeakMap entry is automatically garbage collected
// Regular Map would keep element alive forever — a memory leak

// WeakSet: unique objects, held weakly
const seen = new WeakSet()
function processOnce(obj) {
  if (seen.has(obj)) return
  seen.add(obj)
  doWork(obj)
}
// obj can be garbage collected even while in the WeakSet
// WeakMap and WeakSet: no .size, not iterable — by design (GC timing is unpredictable)

Common Misconceptions

⚠️

Many devs think Map and Object are interchangeable — but actually they have fundamental differences that determine when to use each. Objects coerce all keys to strings (so obj[1] and obj['1'] are the same key), are not safe with user-controlled keys (prototype chain can be polluted), and don't maintain reliable insertion order for numeric keys. Maps take any key type without coercion, have no prototype pollution risk, and always iterate in strict insertion order.

⚠️

Many devs think Set uses deep equality for uniqueness — but actually Set uses SameValueZero comparison (same as === except NaN equals NaN). Two objects with identical content are NOT equal in a Set unless they are the same reference. new Set([{a:1}, {a:1}]).size is 2, not 1. For content-based deduplication of objects, you must serialize to a string first or use a different approach.

⚠️

Many devs think checking .length to get a Map's size works — but actually Maps and Sets use .size (a property), not .length. .length on a Map is undefined. This is a consistent gotcha when switching between arrays (.length) and Maps/Sets (.size).

⚠️

Many devs think WeakMap and WeakSet are just memory-efficient versions of Map and Set — but actually WeakMap and WeakSet have fundamentally different capability: they are not iterable and have no .size property. You cannot loop through them or know how many entries they have. They exist specifically for use cases where you need to associate data with an object without preventing its garbage collection.

⚠️

Many devs think the forEach callback on a Map receives (key, value) like a regular callback — but actually Map's forEach receives (value, key, map) — value first, then key, opposite of what you'd expect from Object.entries() which gives [key, value]. The for...of loop with destructuring ([key, value] of map) is much clearer and avoids this ordering confusion.

⚠️

Many devs think converting an Object to a Map and back is lossless — but actually Object.fromEntries(map) only works when all keys are strings or Symbols. A Map with numeric or object keys produces an Object where those keys are coerced to strings, losing the original key types. The round-trip is lossless only for string-keyed Maps.

Where You'll See This in Real Code

React's useRef and external DOM-node associations use WeakMap internally — when React needs to store metadata associated with a DOM node (fiber data, event handler references), WeakMap ensures the metadata is automatically cleaned up when the DOM node is removed, preventing the memory leaks that would occur with a regular Map.

Memoization with object keys requires Map — a function that caches results by object identity (const cache = new Map(); if(cache.has(inputObj)) return cache.get(inputObj)) is only possible with Map. Using a plain object as cache would coerce the object key to '[object Object]', making every call return the same cached result regardless of which object was passed.

Duplicate detection in streaming data — processing millions of events and tracking which IDs have been seen — is a canonical Set use case. new Set() as a "seen" register with set.has(id) for O(1) lookup is standard in data pipeline code. The alternative (an array with .includes()) would be O(n) and becomes unacceptably slow at scale.

Vue 3's reactivity system uses WeakMap to store reactive effect dependencies for objects — each reactive object's property-to-effect mapping is stored in a WeakMap<object, Map<key, Set<effect>>>. The nested Map<Set> structure (a Map of Sets) models the relationship "for this object, this property is depended on by these effects." Understanding Map and Set makes this data structure readable.

Node.js's module cache is a Map-like structure where module file paths (strings) map to module objects. Understanding that repeated require() calls return the cached instance rather than re-executing the module file is the same concept as Map's get/has pattern — look up by key, return cached value if found.

Implementing a graph data structure in JavaScript uses Map<node, Set<neighbor>> — each node maps to a Set of its neighbors. Set membership represents edge existence, and has() gives O(1) edge lookup. This pattern appears in interview questions, pathfinding algorithms, and dependency resolution systems (like how bundlers detect circular imports).

Interview Cheat Sheet

  • Map: any key type, insertion order preserved, .size, no prototype pollution
  • Object: string/Symbol keys only, integer keys sorted first, prototype chain
  • Map beats Object when: keys are non-strings, frequent add/delete, need size, user-controlled keys
  • Object beats Map when: JSON serialization, static known keys, dot notation convenience
  • Set: unique values, O(1) has(), insertion order preserved, SameValueZero equality
  • Set dedup: [...new Set(arr)] — the most common one-liner
  • Set.has() vs Array.includes(): both check membership, but Set is O(1), Array is O(n)
  • WeakMap/WeakSet: object keys only, weakly held, not iterable, no .size — for cache/metadata without memory leaks
  • Map forEach: (value, key) — value first, opposite of Object.entries [key, value]
  • Map ↔ Object: new Map(Object.entries(obj)) and Object.fromEntries(map)
💡

How to Answer in an Interview

  • 1.The O(1) has() vs O(n) includes() is the most important practical point — quantify it with a million-item example
  • 2.Object key coercion to strings is the core "why use Map" answer — demonstrate with obj[1] === obj['1']
  • 3.Set for deduplication in one line is a must-know pattern — every interviewer recognizes it
  • 4.WeakMap for cache-without-memory-leaks is the senior-level answer — most candidates don't know it
  • 5.The Map of Sets pattern for graph adjacency lists is a great live-coding foundation
  • 6.Vue 3's reactive dependency tracking (WeakMap<Map<Set>>) shows you've seen Map/Set in real architecture
📖 Deep Dive Articles
Modern JavaScript: ES6+ Features Every Developer Must Know13 min read

Practice Questions

No questions tagged to this topic yet.

Tag questions in Admin → Questions by setting the "Topic Page" field to javascript-map-set-interview-questions.

Related Topics

JavaScript Array Interview Questions
Intermediate·8–12 Qs
JavaScript Object Interview Questions
Intermediate·8–12 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz