Skip to main content
Arthur Ha

Performance

Use Set for Membership Checks - Not .includes()

February 27, 2025

You're rendering a list of 500 products. Only some are visible to the current user - you have an array of 200 allowed IDs. You filter: products.filter(p => allowedIds.includes(p.id)). Clean.

Except .includes() scans the array every time. For each of 500 products, you're doing up to 200 comparisons. That's 100,000 checks when 500 would do.

The fix: use a Set. Set.has() is O(1). Build the Set once, then every lookup is constant time.

The problem: .includes() per check

TypeScript
const allowedIds = ["a", "b", "c" /* ... 200 more */];

const visibleProducts = products.filter((item) => allowedIds.includes(item.id));
// 500 products × up to 200 checks each = O(n × m)

Every .includes() walks the array until it finds a match or reaches the end. Repeated lookups, repeated scans.

The fix: Set for O(1) lookup

TypeScript
const allowedIds = new Set(["a", "b", "c" /* ... 200 more */]);

const visibleProducts = products.filter((item) => allowedIds.has(item.id));
// 500 products × 1 lookup each = O(n)

Build the Set once - O(m). Each .has() is O(1). Total: O(n + m) instead of O(n × m).

Same idea with Map for key-value lookups

When you need to look up a value by key (not just membership):

TypeScript
// O(n) per lookup
const user = users.find((u) => u.id === order.userId);

// O(1) per lookup
const userById = new Map(users.map((u) => [u.id, u]));
const user = userById.get(order.userId);

Rule of thumb: Repeated membership checks → Set. Repeated lookups by key → Map. Both give you O(1) instead of O(n).