Index once, loop once

Index with a Map or Set, walk arrays once, and return early.

JSFiddlejsfiddle.net/qw1jabsx/4
Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see the fiddle.
Spread argument limitOpen the fiddle ↗

From vercel-react-best-practices: js-index-maps, js-set-map-lookups and 5 more.

Build the index once

Drag N, or run one lookup to watch it.

users.find per orderBad
Comparisons
0
Time
0 ms
1 lookup · 40 users0 checks
orders.map(o => users.find(u => u.id === o.userId))
Map built onceGood
Operations
0
Time
0 ms
1 lookup · 40 users0 checks
userById.get(o.userId)
// Build once: O(n). Each lookup after that: O(1).
const userById = new Map(users.map(u => [u.id, u]))
const joined = orders.map(o => ({ ...o, user: userById.get(o.userId) }))

// Membership, same idea
const allowedIds = new Set(allowed)
const visible = items.filter(i => allowedIds.has(i.id))

Walk the array once

Press Run. Each lit chip is one read.

Three .filter() callsBad

0 reads · 0 passes

users.filter(u => u.isAdmin) ×3
One for…of loopGood

0 reads · 0 passes

for (const u of users) { … }
const admins = [], testers = [], inactive = []
for (const user of users) {
  if (user.isAdmin) admins.push(user)
  if (user.isTester) testers.push(user)
  if (!user.isActive) inactive.push(user)
}

Check length first

Drag the selection; any length but 100 means changed.

sort().join() !==Bad
Steps
0
String chars
0
Result
–
Input mutated
–
current.sort().join() !== original.sort().join()
Length check firstGood
Steps
0
String chars
0
Result
–
Input mutated
–

–

if (current.length !== original.length) return true
function hasChanges(current, original) {
  if (current.length !== original.length) return true
  const a = current.toSorted()
  const b = original.toSorted()
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return true
  }
  return false
}

Why it matters, pros and cons

Why it matters

.find() in a loop is O(n²); a Map makes it O(n). Each extra pass walks again.

Pros

  • Cost grows with n, not n².
  • Fewer intermediate arrays, less garbage collection.
  • toSorted never mutates props or state.

Cons

  • For a single lookup, .find() beats building a Map.
  • For a handful of items, chained .filter reads better.
  • Build the index in useMemo, not every render.