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.
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) ×3One 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)
}
// One pass, no intermediate array of names-or-false
const names = users.flatMap(u => u.isActive ? [u.name] : [])
// Same shape for parse-and-validate
const nums = strings.flatMap(s => {
const n = parseInt(s, 10)
return isNaN(n) ? [] : [n]
})
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 truefunction 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.
toSortednever mutates props or state.
Cons
- For a single lookup,
.find()beats building a Map. - For a handful of items, chained
.filterreads better. - Build the index in
useMemo, not every render.