JavaScript · Arrays & Iteration

JavaScript Array Searching & Sorting — indexOf, find, the sort() Gotcha and Comparators

6 min read Updated 2026-06-18

Practice Searching & Sorting interview questions

Finding and ordering data

Two of the most common things you do with arrays are find an element and put elements in order. JavaScript provides several searching methods with subtly different behavior, and a sorting method with one of the language's most infamous gotchas. Getting these right — and knowing the traps — is everyday-essential and a frequent interview topic. This guide covers how to search effectively and how to sort correctly, including objects and multi-key cases.

Searching by value: indexOf, lastIndexOf, includes

indexOf returns the index of the first strictly-equal (===) element, or -1 if absent. lastIndexOf does the same from the end. includes returns a boolean.

const a = ['x', 'y', 'z', 'y']
a.indexOf('y')      // 1
a.lastIndexOf('y')  // 3
a.includes('y')     // true when you just need yes/no
a.indexOf('q')      // -1 (not found)

A classic bug is treating the -1 result as falsy: if (a.indexOf(x)) is wrong because index 0 is also falsy. Compare with !== -1, or use includes when you only need a boolean.

The NaN gotcha

indexOf uses ===, and NaN === NaN is false — so indexOf can never find NaN. includes uses the SameValueZero algorithm, which treats NaN as equal to itself.

[NaN].indexOf(NaN)    // -1 can't find it
[NaN].includes(NaN)   // true

If you need the index of a NaN, use findIndex(Number.isNaN). This difference is a favorite interview question because it reveals whether you know how equality works under each method.

Searching by predicate: find, findIndex

When you're searching for "the element where some condition holds" (especially objects), find and findIndex take a predicate function rather than a value.

const users = [{ id: 1 }, { id: 7 }]
users.find(u => u.id === 7)        // { id: 7 } — the object
users.findIndex(u => u.id === 7)   // 1 — its index
users.includes({ id: 7 })          // false different reference

Note includes/indexOf compare by identity, so they can't find an object "that looks the same." Use find/some with a predicate for value-based object searches. findLast and findLastIndex search from the end — handy for "most recent" lookups.

The sort() string-coercion gotcha

Here's the big one. Array.prototype.sort with no comparator converts elements to strings and compares them by UTF-16 code unit. For numbers, this produces nonsense:

[10, 1, 2, 20].sort()             // [1, 10, 2, 20] "10" sorts before "2"
[10, 1, 2, 20].sort((a, b) => a - b)   // [1, 2, 10, 20]

Always pass a comparator when sorting numbers. This default trips up nearly every developer at least once.

Writing comparators

A comparator returns a negative number if a should come first, positive if b should, and 0 to keep their order.

arr.sort((a, b) => a - b)   // ascending numbers
arr.sort((a, b) => b - a)   // descending numbers

The a - b subtraction trick works only for numbers. Don't return a boolean (a > b) — it coerces to 0/1, never negative, so the sort is broken. For strings, use localeCompare:

words.sort((a, b) => a.localeCompare(b))   // human-friendly string order

sort mutates — beware

sort (and reverse) sort in place and return the same array reference. If the array is shared, you've just mutated everyone's copy.

const a = [3, 1, 2]
const sorted = a.sort((x, y) => x - y)
sorted === a   // true original mutated

const safe = a.toSorted((x, y) => x - y)   // ES2023 new array, a untouched
const safe2 = [...a].sort((x, y) => x - y) // copy-then-sort

In immutable contexts (React state), always copy first or use toSorted.

Stable sort and multi-key sorting

Since ES2019, sort is guaranteed stable: elements the comparator treats as equal keep their original relative order. This makes multi-key sorting reliable — sort by the lowest-priority key first, then the next, and so on.

people.sort((a, b) => a.firstName.localeCompare(b.firstName))  // tiebreaker
      .sort((a, b) => a.age - b.age)                            // primary key
// stability keeps name order within each age group

A cleaner single-pass approach uses the || fall-through trick: return the first non-zero comparison.

people.sort((a, b) =>
  a.lastName.localeCompare(b.lastName) ||   // primary
  a.firstName.localeCompare(b.firstName) || // tiebreaker
  a.age - b.age                             // final tiebreaker
)

Locale-aware and natural sorting

localeCompare accepts options for case-insensitive and "natural" numeric sorting (so file2 comes before file10):

['file10', 'file2'].sort((a, b) =>
  a.localeCompare(b, undefined, { numeric: true }))   // ['file2', 'file10']

words.sort((a, b) =>
  a.localeCompare(b, undefined, { sensitivity: 'base' }))  // case/accent-insensitive

For large arrays, build a reusable Intl.Collator and pass its compare method — it's much faster than calling localeCompare per pair.

When to reach for a Set or Map

Repeated includes/indexOf lookups are O(n) each. If you search the same collection many times, build a Set once for O(1) membership tests.

const seen = new Set(bigArray)
queries.filter(q => seen.has(q))   // O(1) per lookup

Likewise, if you constantly look elements up by a key, index them in a Map rather than scanning with find every time. Choosing the right data structure beats optimizing the search.

Key takeaways

  • indexOf/lastIndexOf find by === (return -1 when absent); includes returns a boolean and, unlike indexOf, can find NaN.
  • Use find/findIndex for predicate/object searches; includes compares objects by identity.
  • sort() with no comparator stringifies elements — always pass a comparator for numbers.
  • Comparators return negative/positive/zero; a - b for numbers, localeCompare for strings.
  • sort/reverse mutate in place; use toSorted/toReversed or copy first for immutability.
  • Sort is stable (ES2019+), enabling multi-key sorting; use Intl options for case- insensitive and natural ordering, and a Set/Map for repeated lookups.

Search with the method that matches your equality needs, and sort with a deliberate comparator — and the array's most notorious gotcha stops being a problem.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.