Articles

JavaScript Map and Set: When to Use Them Over Objects

JavaScript's Map and Set are built-in collections with cleaner semantics than plain objects and arrays. Here's how each works and when to reach for one.

Takina Takina · · 4 min read
Laptop screen showing JavaScript code

Map and Set are built-in JavaScript collection types that fix specific problems with using plain objects as key-value stores and arrays as uniqueness-tracking lists. They’ve been part of the language for years, but plenty of code still reaches for {} and [] out of habit in places where Map and Set are both faster and clearer.

Map: key-value pairs without the object baggage

A plain object works as a key-value store, but it comes with baggage: keys are coerced to strings (or symbols), every object inherits properties from Object.prototype unless you’re careful, and there’s no direct way to know how many entries it has without Object.keys(obj).length.

Map fixes all three:

const cache = new Map();
cache.set(userObject, "some value");   // any value can be a key, including objects
cache.set(42, "numeric key");           // no coercion to "42"
cache.size;                             // O(1), no Object.keys() needed
cache.has(userObject);                  // true

Because Map doesn’t coerce keys, cache.get(42) and cache.get("42") are genuinely different entries — a plain object would collapse them into the same string key. That makes Map the right choice any time your keys aren’t naturally strings: object references (useful for metadata caches keyed by DOM nodes or instances), numbers, or even other collections.

Map is also directly iterable in insertion order, which plain objects only guarantee informally:

for (const [key, value] of cache) {
  console.log(key, value);
}

Set: uniqueness without manual deduping

Set stores unique values — adding a duplicate is a no-op instead of an error or a silent overwrite. The classic use is deduplicating an array in one line:

const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]

But Set is also the right structure any time you’re checking membership repeatedly. An array’s .includes() is an O(n) scan every time; a Set’s .has() is O(1) on average, the same algorithmic advantage a hash table gives you over a linear scan — see our piece on hash tables if you want the underlying mechanics. For a large list you’re checking against repeatedly (permissions, visited IDs, seen tokens), that difference compounds fast.

const seenIds = new Set();
for (const item of items) {
  if (seenIds.has(item.id)) continue;
  seenIds.add(item.id);
  process(item);
}

Map vs plain objects

MapPlain object
Key typesAny value, including objectsStrings and symbols only
Key orderInsertion order, guaranteedInsertion order, mostly — integer-like keys sort first
Size.size propertyObject.keys(obj).length
IterationDirectly iterableNeeds Object.keys/entries/values
Prototype pollution riskNoneInherits from Object.prototype unless created with Object.create(null)
JSON serializationNot directly serializableNative JSON.stringify support

That last row is the real reason plain objects haven’t disappeared: if you need to serialize the structure to JSON — for an API response, for localStorage, for a config file — a plain object (or array, for Set) is still more convenient. Map and Set need an explicit conversion step (Object.fromEntries(map) or [...set]) first.

WeakMap and WeakSet

WeakMap and WeakSet are variants that only accept object keys/values and don’t prevent those objects from being garbage collected. That makes them useful for attaching metadata to objects you don’t own the lifecycle of — a cache of computed data keyed by DOM node, for example, that should disappear automatically once the node is removed and nothing else references it. They’re not iterable and have no .size, precisely because their contents can shrink at any moment outside your control.

When to actually reach for these

Use Map when your keys aren’t strings, when you need reliable size and iteration order, or when you’re building any kind of cache or lookup table at meaningful scale. Use Set any time you’re deduplicating or checking membership more than a couple of times. For small, string-keyed configuration objects that map directly to JSON, a plain object is still simpler and there’s no reason to change it.

If you’re profiling a hot path and the built-in coercion or .includes() scan shows up in a flame graph, swapping in Map or Set is usually a drop-in fix — see our guide on debouncing vs throttling for another common source of avoidable per-frame work in the same category.

The takeaway

Map and Set aren’t replacements for objects and arrays everywhere — they’re the right tool when keys need to be non-strings, when membership checks happen in a loop, or when insertion order and size need to be reliable without workarounds. Reach for a plain object when you need JSON serialization or a small fixed shape; reach for Map or Set when you’re building an actual lookup structure.

Takina Takina · · 5 min read

Promise.all() vs allSettled() vs race() Compared

Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

JavaScript Spread vs Rest Operators, Explained

The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

ResizeObserver API Explained: Watching Element Size

The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.

#Web Development #JavaScript #Frontend