CodePark/ lessons / Arrays, Maps and Sets: pick the right box
START PRACTICINGPRACTICE
Data structures and algorithms / Foundations / lesson

Arrays, Maps and Sets: pick the right box

The idea

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) and arr.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) and arr.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) and arr.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 example new Set([1, 2, 2]) holds 1 and 2.
  • set.add(x) adds a value. Adding the same value twice does nothing.
  • set.has(x) returns true or false.
  • set.delete(x) removes, and set.size is 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, or undefined if the key isn't there.
  • map.has(key) returns true or false.
  • map.delete(key) removes a pair, and map.size is 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] and obj["1"] are the same slot. A Map keeps 1 and "1" apart, and can use any value as a key.
  • Size. map.size is O(1). An object needs Object.keys(obj).length, which builds an array: O(n).
  • No surprises. An object inherits keys like "constructor" and "toString", so "constructor" in {} is true. 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 be true. 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 be 0 or "". Those are falsy, so it looks missing. Use map.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).