Map vs Array.findIndex in JavaScript – When to Use Each
When you have a list of items and need to find one by id over and over—in a hot path, a render loop, or a large dataset—the choice between scanning the array (findIndex / find) and looking up by key (Map or object) has a big impact. This post explains both approaches, their complexity, and when to use each, with runnable examples.
The same comparison is available as an interactive demo with a live benchmark — use the Try demo link in the header above.
What we're comparing
Array + findIndex: You keep an array of objects. To get an item by id you do arr.findIndex(item => item.id === id) (or find). Each lookup walks the array until it finds a match — O(n) per lookup.
Map (or object): You keep a Map (or plain object) from id → item. To get an item by id you do map.get(id) or obj[id]. Each lookup is a single key access — O(1) average.
Same “logical” data: a collection of items identifiable by id. Different structures, different lookup cost.
Example: a list of users
We'll use a small user list in both shapes so you can compare.
Array + findIndex
type User = { id: string; name: string; role: string }
const usersArray: User[] = [
{ id: '1', name: 'Alice', role: 'admin' },
{ id: '2', name: 'Bob', role: 'user' },
{ id: '3', name: 'Carol', role: 'editor' },
]
Get one user by id:
function getUserByIndex(users: User[], id: string): User | undefined {
const index = users.findIndex((u) => u.id === id)
return index === -1 ? undefined : users[index]
}
Or with find:
function getUserFind(users: User[], id: string): User | undefined {
return users.find((u) => u.id === id)
}
Both scan the array; average cost grows with length (O(n)).
Map by id
type User = { id: string; name: string; role: string }
// Build once from array (e.g. from API)
const usersArray: User[] = [
{ id: '1', name: 'Alice', role: 'admin' },
{ id: '2', name: 'Bob', role: 'user' },
{ id: '3', name: 'Carol', role: 'editor' },
]
const usersById = new Map<string, User>(
usersArray.map((u) => [u.id, u])
)
Get one user by id:
function getUserByMap(map: Map<string, User>, id: string): User | undefined {
return map.get(id)
}
Lookup is O(1) on average. You do pay an upfront cost to build the Map; if the list never changes, that’s a one-time O(n).
Realtime example: lookup in a loop
Imagine you’re rendering a list of “comments” and for each comment you need the author (user) by authorId. If you only have an array of users:
// O(comments.length * users.length) — can get slow
comments.forEach((comment) => {
const author = users.find((u) => u.id === comment.authorId)
renderComment(comment, author)
})
If you have a Map:
// O(comments.length) — each author lookup is O(1)
comments.forEach((comment) => {
const author = usersById.get(comment.authorId)
renderComment(comment, author)
})
Same UI, different cost. For dozens of comments and users it might not matter; for hundreds or thousands, Map wins.
Building and maintaining the Map
One-time build (read-only list):
const usersById = new Map(usersArray.map((u) => [u.id, u]))
When the list can change: you must keep the Map in sync. Add/update/delete in both the array (if you still need order) and the Map.
function addUser(
array: User[],
map: Map<string, User>,
user: User
): void {
array.push(user)
map.set(user.id, user)
}
function updateUser(
array: User[],
map: Map<string, User>,
id: string,
patch: Partial<User>
): void {
const index = array.findIndex((u) => u.id === id)
if (index === -1) return
const updated = { ...array[index], ...patch }
array[index] = updated
map.set(id, updated)
}
function removeUser(
array: User[],
map: Map<string, User>,
id: string
): void {
const index = array.findIndex((u) => u.id === id)
if (index === -1) return
array.splice(index, 1)
map.delete(id)
}
So: Map gives O(1) lookup but you pay with extra structure and sync logic. For a static or rarely-changing list, build the Map once and enjoy O(1) lookups.
Map vs plain object (Record<string, User>)
For string (or number) keys, a plain object works too:
const usersById: Record<string, User> = {}
usersArray.forEach((u) => { usersById[u.id] = u })
// lookup: usersById[id]
- Map: Better when keys might not be strings, you need insertion order, or you want to avoid prototype pollution. Slightly more verbose (
map.get(id)vsobj[id]). - Object: Slightly less code and very fast for string keys. Use when keys are plain strings and you don’t need Map’s extras.
Complexity is the same: O(1) lookup. The benchmark demo uses Map; in practice object lookup is comparable.
Pros and cons
Array + findIndex — pros
- Single source of structure: Only one collection; no duplicate data or sync logic.
- Order is explicit: The array order is the list order; easy to sort, filter, slice.
- Simple for small lists: For tens of items and few lookups, code is straightforward and performance is fine.
- Fits API responses: Many APIs return
[{ id, ... }, ...]; you can use that array as-is.
Array + findIndex — cons
- Lookup is O(n): Every
find/findIndexcan scan the whole array. Cost grows with list size and lookup count. - Hot paths hurt: If you lookup by id inside a render loop or in a tight loop, the cost multiplies (e.g. comments × users).
- Update/delete by id also O(n): You need the index first, so you still pay a scan (or maintain a separate index).
Map (or object) — pros
- O(1) lookup by id: Constant-time access; scales to large lists and many lookups.
- Predictable cost: Lookup time doesn’t depend on list size.
- Good for “get by id” hot paths: Rendering, event handlers, merging by id, etc.
Map (or object) — cons
- Extra structure: You store both the array (if you need order) and the Map; more memory and code.
- Must keep in sync: Add/update/delete must touch both; bugs can leave them out of sync.
- Overkill for tiny lists: For a handful of items and rare lookups, the benefit is small.
When to use which
- Prefer array + findIndex when:
- The list is small (e.g. < 50 items) and you don’t lookup by id often.
- You only need the list in order and never “get by id” in a hot path.
- You want minimal code and no extra structure.
- Prefer Map (or object) when:
- You need “get by id” often or in a loop (e.g. render comments with authors).
- The list is large or the number of lookups is high.
- You already have normalized state (e.g. entities by id) — then you’re already doing O(1) lookup.
You can mix: keep an array for “list in order” and a Map for “get by id”. Build the Map once from the array when data loads; if the list is mutable, update both on add/update/delete.
Summary
- Array + findIndex: Simple, one structure, O(n) per lookup. Good for small lists and rare lookups.
- Map (or object): O(1) lookup by id, better for large lists and hot paths. Requires building and optionally maintaining a second structure.
- Use the demo (link above) to run a live benchmark and see the difference at different list sizes and lookup counts.