Each structure is fast at one kind of question. An array answers "what's at position 3?" in one step. A Map or Set answers "is this thing here?" in one step.
Think of it like this: an array is a row of numbered lockers. You can walk straight to locker 42. But to find the locker with your red scarf, you have to open them one by one. A Map is a coat check. You hand over your ticket, and the attendant goes straight to your coat.
Arrays: great at positions
An array stores values in order. Each value is an element, and its position is its index. Indexes start at 0, so the last element of an array of length 5 is at index 4.
Arrays are fast when you know the index:
arr[i]reads or writes one element in O(1). That's one step, however long the array is.arr.push(x)andarr.pop()add or remove at the end in O(1).
Arrays are slow when you don't know the index:
arr.includes(x),arr.indexOf(x)andarr.find(fn)start at index 0 and check each element. Say there are 1,000,000 elements and the value is at the end, or missing. That's 1,000,000 checks: O(n).arr.shift(),arr.unshift(x)andarr.splice(i, 1)change the front or middle, so every later element has to move one place. Also O(n).
const scores = [40, 75, 90, 62];
scores[2]; // 90, O(1): we know the index
scores.push(88); // O(1): adds at the end
scores.includes(62); // true, O(n): checks 40, 75, 90, 62 in turn
scores.indexOf(100); // -1, O(n): had to check every element to be sure
Set: a bag of unique values
A Set holds values with no duplicates and answers "is this value in here?" in O(1). It doesn't care about positions.
new Set(iterable)builds one, for examplenew Set([1, 2, 2])holds 1 and 2.set.add(x)adds a value. Adding the same value twice does nothing.set.has(x)returnstrueorfalse.set.delete(x)removes, andset.sizeis the count.for (const x of set)visits values in the order they were first added.
Reach for a Set when the only question is "have I seen this?" or "is this allowed?".
const selectedIds = new Set<string>();
selectedIds.add("u1");
selectedIds.add("u7");
selectedIds.add("u1"); // already there, ignored
selectedIds.has("u7"); // true, O(1)
selectedIds.size; // 2
const unique = [...new Set([3, 1, 3, 2, 1])]; // [3, 1, 2]: removes duplicates, keeps first-seen order
Map: look things up by key
A Map stores key → value pairs. Given a key, it finds the value in O(1). A key is the thing you look up by, such as a user id, a word or a number. The value is what you stored about it, such as the user object, a count or an index.
map.set(key, value)stores or overwrites.map.get(key)returns the value, orundefinedif the key isn't there.map.has(key)returnstrueorfalse.map.delete(key)removes a pair, andmap.sizeis the count.for (const [key, value] of map)visits pairs in insertion order.
A very common line is map.get(key) ?? 0: "the stored number, or 0 if there's nothing yet". You'll use it whenever you count things.
type User = { id: string; name: string };
const users: User[] = [
{ id: "u1", name: "Ada" },
{ id: "u7", name: "Lin" },
];
// Build the lookup once: O(n)
const byId = new Map<string, User>();
for (const u of users) byId.set(u.id, u);
// Then every lookup is O(1) instead of users.find(...), which is O(n)
byId.get("u7"); // { id: "u7", name: "Lin" }
byId.get("u9"); // undefined
byId.has("u1"); // true
// "stored number, or 0": the start of every counting loop
const clicks = new Map<string, number>();
clicks.set("buy", (clicks.get("buy") ?? 0) + 1); // buy → 1
clicks.set("buy", (clicks.get("buy") ?? 0) + 1); // buy → 2
Map or plain object?
A plain object {} can also map keys to values. In interviews, prefer Map:
- Key types. Object keys are always strings:
obj[1]andobj["1"]are the same slot. A Map keeps1and"1"apart, and can use any value as a key. - Size.
map.sizeis O(1). An object needsObject.keys(obj).length, which builds an array: O(n). - No surprises. An object inherits keys like
"constructor"and"toString", so"constructor" in {}istrue. A Map starts truly empty. - Order. A Map always iterates in insertion order. Objects put keys that look like whole numbers, such as
"2", first and in number order.
A plain object is fine for a fixed record with known field names, such as { name, age }. It's also fine when you need JSON.
Which one do I reach for?
I need to... Use
get the item at position i array: arr[i]
keep things in order, add/remove at the end array: push / pop
know if I've seen a value before Set: has / add
remove duplicates Set
look something up by id or name Map: get / set
count how many times each thing appears Map: value -> count
remember where I saw a value Map: value -> index
group items that share something Map: key -> array of items
The costs side by side:
array Set Map
read by index O(1) - -
add at end / add O(1) O(1) O(1)
"is x in here?" O(n) O(1) O(1) (by key)
remove from the front O(n) O(1) O(1)
Common mistakes
- Using
arr.includes(x)inside a loop to check "seen before". It's O(n) per check, so the loop becomes O(n²). Use a Set. - Putting arrays or objects in a Set and expecting
set.has([1, 2])to betrue. They compare by reference, so a new[1, 2]is a different value. Use a string key such as"1,2". - Writing
if (map.get(k))when the stored value can be0or"". Those are falsy, so it looks missing. Usemap.has(k). - Using an object with number keys and getting strings back from
Object.keys. A Map keeps the original key type. - Calling
users.find(u => u.id === id)inside a loop. Build a Map from id to user once, then look up in O(1).