Sparround

Set and Map usage patterns

Set and Map are built on the same hash mechanism but answer different questions:

  • Set — the "does this value exist?" question. It stores membership only, with no value. Its two main jobs: removing duplicates (dedup) and keeping an "already seen" record.
  • Map — the "what corresponds to this key?" question. It builds a key → value index.

Simple rule: if the answer is yes/no, use a Set; if the answer is some data, use a Map.

The patterns you will meet most often:

  • Dedup: [...new Set(emails)]
  • Membership check: if (blocked.has(userId))includes on an array is O(n), a Set is O(1)
  • Building an index: const byId = new Map(users.map(u => [u.id, u])) — every later lookup is O(1)
  • Grouping: key → the list of elements belonging to that key
  • Counting: key → how many times it appeared

Key equality — this is where the languages genuinely diverge, and it is the most common interview trap.

JavaScript: Map/Set use SameValueZero comparison. For primitives that behaves like === (except NaN, which counts as equal to itself). Object keys, however, compare by reference — call m.set({ id: 1 }, 'a') and then m.get({ id: 1 }) and you get undefined, because that is a second, entirely different object.

If you need value-based keys in JS, you have to build the key as a string yourself (` ${a}:${b} `).

Kotlin: equality is defined by the equals()/hashCode() pair. A data class generates both automatically, so value-based keys come for free: Point(1, 2) == Point(1, 2)true, therefore the same key in a map. A plain class compares by reference — the same trap as JS.

Dart: the default is reference-based. For value semantics you must override operator == and hashCode together — writing one and forgetting the other breaks the map. To avoid the boilerplate people use package:equatable or freezed; in Dart 3 records ((1, 2)) already compare by value and can be used as ready-made keys.

QuestionJavaScriptKotlinDart
How do you get a value-based key?By building a string key by hand`data class` — automaticOverride `==` + `hashCode`, or a Dart 3 record
Iteration order`Map`/`Set` preserve insertion order (guaranteed)`HashMap` — none; `LinkedHashMap` — insertion; `TreeMap` — sortedThe `{}` literal is a `LinkedHashMap` — insertion order; `HashMap` — none
If you need keys in sorted orderNo built-in — extract the keys and `sort``sortedMapOf` / `java.util.TreeMap``SplayTreeMap` (`dart:collection`)
Size`map.size``map.size``map.length`

When is a plain object/record not a Map?

In JS many people use {} as a map. For a small, fixed configuration that is fine, but for dynamic keys Map is almost always the right choice:

  • Object keys can only be string or symbolobj[1] is really obj["1"]. Map accepts any type as a key, even an object.
  • An object inherits the prototype chain: "toString" in obj is true even if you added nothing. A Map starts clean.
  • Keys like constructor or __proto__ produce surprises (prototype pollution risk).
  • Size: Object.keys(obj).length is O(n), map.size is O(1).
  • Frequent insert/delete: Map is optimised for it, plain objects are not.

Rule: object/record when the keys are known and fixed up front, Map when the keys arrive at runtime. Kotlin and Dart have no such confusion, because there an object (data class) and a map are different things at the language level.

Interview tip. The most frequent question here: "Can you use an object as a Map key?" The weak answer is just "yes". The strong answer separates the languages:

"Yes, but what matters is how equality is defined. In JS object keys compare by reference, so two separate objects with identical contents are different keys — if I need value-based keys I build a string key myself. In Kotlin a data class generates equals/hashCode, so it works by value. In Dart I have to override == and hashCode together."

A second classic: "What is the difference between Object.keys().length and map.size?" — the answer is O(n) versus O(1). Interviewers use this small detail to probe your feel for performance.

The most commonly missed point: giving a wrong guarantee about iteration order. JS Map order IS guaranteed; Kotlin HashMap order is NOT. Confusing the two is a real source of bugs.

📚 Sources and documentation

  • Setofficialdeveloper.mozilla.org