Demo Type · 01

Compare a few ways to solve the same problem

Use this when there are 3–4 reasonable solutions and the lesson is the trade-off — side-by-side cards with the code, what's good, what's not, and a plain "pick this when" for each.

This is a copyable exemplar. To use it in a course, lift the <section class="demo"> block below into a lesson built from assets/lesson-template.html — the design tokens and component CSS already match.

Read the plain version, or open the technical layer on any section.
1

The big idea


Say you have a list with repeats in it — [3, 1, 3, 2, 1] — and you want one clean copy with each value kept only once: [3, 1, 2]. In JavaScript there are at least three honest ways to do that. None of them is "the right answer." Each makes a different bargain between how short it is, how fast it runs, and how much it can handle.

The job of this lesson is not to pick a winner. It is to lay the three side by side so you can see the bargain each one makes, and choose on purpose.

Think of it like… three routes home on a map. The freeway is fastest but you have to get on it. The shortcut through town is easy to remember but slow at rush hour. The scenic road lets you stop wherever you like but takes the longest. Same destination, different reasons to choose each.

What "de-duplicate" means here

We keep the first occurrence of each value and preserve order. Equality is JavaScript's SameValueZero — the same rule a Set uses — so NaN de-dupes correctly and +0/-0 collapse together. All three approaches below produce [3, 1, 2] for the input [3, 1, 3, 2, 1]; they differ in cost and in how they behave as the list grows.

2

Three ways, side by side


Each card shows the actual code, what it's good at, where it bites, and a one-liner for when to reach for it. Hover or focus a card to bring it forward.

A

new Set()

Pour the list into a Set — which can't hold duplicates — then spread it back into an array.

const unique = function (list) {
  return [...new Set(list)];
};

unique([3, 1, 3, 2, 1]);
// → [3, 1, 2]

Pros

  • +Shortest to read and write — one line.
  • +Fast: roughly linear, scales to big lists.
  • +Keeps original order; handles NaN.

Cons

  • De-dupes by identity only — no custom "same".
  • Objects compared by reference, not contents.
Pick this when The values are primitives (numbers, strings) and you want the simplest, fastest thing. This is the default.
B

filter() + indexOf

Keep an item only if this is the first spot it appears — i.e. its index equals its first index.

const unique = function (list) {
  return list.filter(function (v, i) {
    return list.indexOf(v) === i;
  });
};

unique([3, 1, 3, 2, 1]);
// → [3, 1, 2]

Pros

  • +No Set — works in very old runtimes.
  • +Reads like its definition: "first time seen".

Cons

  • Slow: scans the list inside the list — quadratic.
  • indexOf can't find NaN, so it stays duplicated.
Pick this when The list is small and you want one self-explanatory line with no extra data structure.
C

reduce() + seen-map

Walk once, remembering what you've seen in a Map. Push a value only the first time — and pick your own "same" rule.

const unique = function (list, keyOf) {
  keyOf = keyOf || (function (v) { return v; });
  const seen = new Map();
  return list.reduce(function (out, v) {
    const k = keyOf(v);
    if (!seen.has(k)) { seen.set(k, 1); out.push(v); }
    return out;
  }, []);
};

Pros

  • +Fast: one pass, linear like the Set.
  • +Custom key — de-dupe objects by a field.

Cons

  • Most code to read; easiest to get subtly wrong.
  • Overkill when a plain Set would do.
Pick this when You need "same" to mean something specific — e.g. users with the same id — or want to count along the way.
3

Which should I reach for?


Pick the line that matches your situation and the matching approach lights up below, with one sentence on why.

My list is mostly…

4

In one picture


The real divider is how each one scales. As the list gets longer, the filter()+indexOf route climbs much faster than the other two.

work list size → filter()+indexOf — steep (n²) new Set() — gentle (n) reduce()+map — gentle (n)
Read it left → right: small lists, everyone's fine. Long lists, only the linear pair stays cheap.

Why the red curve bends up

filter() calls its test once per item (that's n calls), and indexOf inside it scans the array again each time (up to n more) — so total work grows like . Set and the Map seen-store do a single average-O(1) lookup per item, so total work grows like n. At 100 items the difference is invisible; at 100,000 the quadratic version is doing billions of comparisons.

Order & equality

All three preserve first-seen order. Set and the Map key use SameValueZero, so NaN de-dupes; indexOf uses strict ===, which never matches NaN — so approach B leaves duplicate NaNs in. Approach C is the only one that lets you redefine "same" via keyOf.