[{"data":1,"prerenderedAt":174},["ShallowReactive",2],{"qa-\u002Fjavascript\u002Farrays\u002Fsearching-sorting":3},{"page":4,"siblings":158,"blog":171},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":149,"seo":150,"seoDescription":151,"stem":152,"subtopic":153,"topic":154,"topicSlug":155,"updated":156,"__hash__":157},"qa\u002Fjavascript\u002Farrays\u002Fsearching-sorting.md","Searching Sorting",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","JavaScript","javascript",{},true,3,"\u002Fjavascript\u002Farrays\u002Fsearching-sorting",[23,28,32,36,40,44,48,52,56,60,64,68,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145],{"id":24,"difficulty":25,"q":26,"a":27},"indexof-basics","easy","How does indexOf work and what does it return when nothing is found?","**`indexOf`** returns the index of the first element strictly equal\n(`===`) to the search value, or **`-1`** if it is not present.\n\n```js\nconst a = ['x', 'y', 'z']\na.indexOf('y')   \u002F\u002F 1\na.indexOf('q')   \u002F\u002F -1  the \"not found\" sentinel\n```\n\nThe classic pitfall is treating `-1` as falsy: `if (a.indexOf(x))` is\n**wrong** because index `0` is also falsy. Compare explicitly with\n`!== -1`, or prefer `includes` when you only need a boolean.\n",{"id":29,"difficulty":25,"q":30,"a":31},"lastindexof","What is the difference between indexOf and lastIndexOf?","**`indexOf`** searches front-to-back and returns the first match;\n**`lastIndexOf`** searches back-to-front and returns the last match.\nBoth use `===` and return `-1` when absent.\n\n```js\nconst a = [1, 2, 3, 2, 1]\na.indexOf(2)      \u002F\u002F 1\na.lastIndexOf(2)  \u002F\u002F 3  last occurrence\n```\n\nEach also takes an optional start index. A subtle point: for\n`lastIndexOf` the second argument is where to *begin searching\nbackwards*, so `a.lastIndexOf(2, 2)` searches indices 2->0.\n",{"id":33,"difficulty":14,"q":34,"a":35},"includes-vs-indexof","When should you use includes instead of indexOf?","Use **`includes`** when you only care *whether* a value exists — it\nreturns a boolean and reads clearly. Use **`indexOf`** when you need the\n*position*.\n\n```js\nif (tags.includes('urgent')) { \u002F* ... *\u002F }   \u002F\u002F clear intent\nconst at = tags.indexOf('urgent')            \u002F\u002F when you need where\n```\n\nThe other reason to prefer `includes`: it finds `NaN`, which `indexOf`\ncannot (see the next question).\n",{"id":37,"difficulty":14,"q":38,"a":39},"nan-search-gotcha","Why can't indexOf find NaN, but includes can?","`indexOf` compares with `===`, and `NaN === NaN` is **false** — so it can\nnever match. `includes` uses the **SameValueZero** algorithm, which\ntreats `NaN` as equal to `NaN`.\n\n```js\n[NaN].indexOf(NaN)    \u002F\u002F -1  can't find it\n[NaN].includes(NaN)   \u002F\u002F true\n```\n\nSameValueZero is also why `includes(0)` matches both `+0` and `-0`. If\nyou must locate a `NaN` index, use `findIndex(Number.isNaN)`.\n",{"id":41,"difficulty":25,"q":42,"a":43},"find-vs-filter","What is the difference between find and filter?","**`find`** returns the **first element** that satisfies the predicate (or\n`undefined`); **`filter`** returns a **new array** of *all* matches.\n\n```js\nusers.find(u => u.id === 7)     \u002F\u002F the one user, or undefined\nusers.filter(u => u.active)     \u002F\u002F array of every active user\n```\n\nUse `find` for \"give me the one I want\" — it also short-circuits on the\nfirst match, so it's cheaper than `filter()[0]` which scans the whole\narray.\n",{"id":45,"difficulty":25,"q":46,"a":47},"findindex","When would you use findIndex over indexOf?","**`indexOf`** searches by value equality; **`findIndex`** searches by a\n**predicate function**, so it works for objects and complex conditions.\n\n```js\nconst i = users.indexOf(someUser)          \u002F\u002F needs the exact reference\nconst j = users.findIndex(u => u.id === 7) \u002F\u002F search by a property\n```\n\nBoth return `-1` when nothing matches. Reach for `findIndex` whenever the\nmatch is \"the element where some condition holds\" rather than \"this exact\nvalue.\"\n",{"id":49,"difficulty":14,"q":50,"a":51},"findlast","What do findLast and findLastIndex do?","They are the back-to-front counterparts of `find`\u002F`findIndex`: they walk\nthe array **from the end** and return the first matching element \u002F index.\n\n```js\nconst nums = [1, 8, 3, 9, 2]\nnums.findLast(n => n > 4)       \u002F\u002F 9  last match, not first\nnums.findLastIndex(n => n > 4)  \u002F\u002F 3\n```\n\nBefore these (ES2023) you'd reverse a copy or loop manually. They're handy\nfor \"most recent\" lookups in append-ordered data like logs.\n",{"id":53,"difficulty":25,"q":54,"a":55},"some-every-search","How do some and every help with searching?","**`some`** answers \"does *any* element match?\" and **`every`** answers\n\"do *all* elements match?\" — both return a boolean and short-circuit.\n\n```js\nnums.some(n => n \u003C 0)    \u002F\u002F true if there's at least one negative\nnums.every(n => n > 0)   \u002F\u002F true only if all are positive\n```\n\n`some` stops at the first `true`; `every` stops at the first `false`. A\ngotcha: `every` on an **empty array is `true`** (vacuous truth) and `some`\non an empty array is `false`.\n",{"id":57,"difficulty":14,"q":58,"a":59},"sort-default-gotcha","Why does sorting numbers with sort() give wrong results?","Without a comparator, **`sort` converts elements to strings** and compares\nthem by UTF-16 code unit — so numbers sort lexicographically.\n\n```js\n[10, 1, 2, 20].sort()            \u002F\u002F [1, 10, 2, 20]  \"10\" \u003C \"2\"\n[10, 1, 2, 20].sort((a,b)=>a-b)  \u002F\u002F [1, 2, 10, 20]\n```\n\nAlways pass a comparator for numbers. This string-coercion default trips\nup nearly everyone at least once.\n",{"id":61,"difficulty":25,"q":62,"a":63},"numeric-comparator","How do you write an ascending and descending numeric comparator?","A comparator returns a **negative** number if `a` should come first, a\n**positive** number if `b` should, and `0` to leave order unchanged.\n\n```js\narr.sort((a, b) => a - b)   \u002F\u002F ascending\narr.sort((a, b) => b - a)   \u002F\u002F descending\n```\n\nThe `a - b` trick works only for numbers. Don't return a boolean\n(`a > b`) — coerced to `0`\u002F`1` it never yields a negative, so the sort is\nbroken.\n",{"id":65,"difficulty":14,"q":66,"a":67},"sort-mutates","Does sort() mutate the array? How do you sort without mutating?","Yes — **`sort` sorts in place and returns the same array reference**.\nThat can surprise callers sharing the array.\n\n```js\nconst a = [3, 1, 2]\nconst b = a.sort((x,y)=>x-y)\nb === a            \u002F\u002F true  original mutated\n\nconst sorted = [...a].sort((x,y)=>x-y)  \u002F\u002F copy first\nconst safe = a.toSorted((x,y)=>x-y)     \u002F\u002F ES2023, returns new array\n```\n\nPrefer `toSorted` (or spread-then-sort) in code that values immutability,\ne.g. React state.\n",{"id":69,"difficulty":70,"q":71,"a":72},"stable-sort","hard","Is JavaScript's sort stable, and why does that matter?","Since ES2019 `sort` is guaranteed **stable**: elements the comparator\ntreats as equal keep their original relative order. This makes\n**multi-pass sorting** reliable.\n\n```js\n\u002F\u002F sort by name, then by age — age becomes the primary key,\n\u002F\u002F ties broken by the earlier name order\npeople.sort((a,b)=>a.name.localeCompare(b.name))\n      .sort((a,b)=>a.age - b.age)  \u002F\u002F stable keeps name order within an age\n```\n\nBefore ES2019, engines like V8 used an unstable sort for large arrays, so\nthis pattern wasn't portable.\n",{"id":74,"difficulty":14,"q":75,"a":76},"sort-objects","How do you sort an array of objects by a property?","Compare the property inside the comparator. Subtract for numbers, use\n`localeCompare` for strings.\n\n```js\nusers.sort((a, b) => a.age - b.age)              \u002F\u002F numeric field\nusers.sort((a, b) => a.name.localeCompare(b.name)) \u002F\u002F string field\n```\n\nDon't use `a.name > b.name` directly — it returns a boolean and breaks the\ncomparator contract. Remember it mutates, so copy first if needed.\n",{"id":78,"difficulty":70,"q":79,"a":80},"multi-key-sort","How do you sort by multiple keys?","Evaluate keys in priority order and return the first non-zero comparison.\n\n```js\npeople.sort((a, b) =>\n  a.lastName.localeCompare(b.lastName) ||  \u002F\u002F primary\n  a.firstName.localeCompare(b.firstName) || \u002F\u002F tiebreaker\n  a.age - b.age                             \u002F\u002F final tiebreaker\n)\n```\n\nThe `||` chain works because a `0` (equal) is falsy, so it falls through\nto the next key. Clean and avoids nested `if`s.\n",{"id":82,"difficulty":14,"q":83,"a":84},"localecompare","Why use localeCompare for sorting strings?","Comparing strings with `\u003C`\u002F`>` uses raw UTF-16 code units, which mishandles\naccents and locale rules. **`localeCompare`** sorts the way humans expect\nfor a given language.\n\n```js\n['é', 'a', 'z'].sort()                       \u002F\u002F ['a', 'z', 'é']\n['é', 'a', 'z'].sort((a,b)=>a.localeCompare(b)) \u002F\u002F ['a', 'é', 'z']\n```\n\nFor large arrays, build a reusable `Intl.Collator` and pass its `compare`\nmethod — it's much faster than calling `localeCompare` repeatedly.\n",{"id":86,"difficulty":14,"q":87,"a":88},"case-insensitive-sort","How do you sort strings case-insensitively?","Either lowercase both sides, or use `localeCompare` with the\n`sensitivity: 'base'` option (which also ignores accents).\n\n```js\narr.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))\n\u002F\u002F or\narr.sort((a, b) =>\n  a.localeCompare(b, undefined, { sensitivity: 'base' }))  \u002F\u002F\n```\n\nThe `Intl` option is preferable: lowercasing can be wrong for some\nlanguages (e.g. Turkish dotless İ).\n",{"id":90,"difficulty":25,"q":91,"a":92},"reverse-mutates","Does reverse() mutate, and what's the immutable alternative?","Yes — **`reverse` reverses in place** and returns the same array. The\nES2023 immutable version is **`toReversed`**.\n\n```js\nconst a = [1, 2, 3]\na.reverse()        \u002F\u002F a is now [3, 2, 1]  mutated\nconst b = a.toReversed()  \u002F\u002F new array, a untouched\n```\n\nTo reverse-sort numbers, you don't need `reverse` at all — just flip the\ncomparator: `sort((x,y)=>y-x)`.\n",{"id":94,"difficulty":14,"q":95,"a":96},"indexof-fromindex","How does the second argument to indexOf and includes work?","It's the **start index**. Searching begins there and continues to the end.\nNegative values count from the end.\n\n```js\nconst a = ['a', 'b', 'a', 'b']\na.indexOf('a', 1)    \u002F\u002F 2  (skips index 0)\na.includes('a', -1)  \u002F\u002F false (only looks at last element)\n```\n\nUseful for finding *all* occurrences in a loop: keep calling\n`indexOf(x, lastFound + 1)` until it returns `-1`.\n",{"id":98,"difficulty":70,"q":99,"a":100},"find-all-indices","How do you find all indices where a condition holds?","There's no built-in, but `reduce` collects them in one pass, or `map` +\n`filter`.\n\n```js\nconst evens = nums.reduce((acc, n, i) => {\n  if (n % 2 === 0) acc.push(i)\n  return acc\n}, [])  \u002F\u002F array of indices\n```\n\nAvoid the naive `nums.map((n,i)=> n%2===0 ? i : -1).filter(i=>i!==-1)` —\nit's two passes and the `-1` sentinel is fragile if `0` is a valid index.\n",{"id":102,"difficulty":70,"q":103,"a":104},"binary-search","When is a manual binary search worthwhile over includes\u002FindexOf?","`includes`\u002F`indexOf` are **O(n)** linear scans. If the array is **already\nsorted** and you search it many times, a **binary search** is O(log n).\n\n```js\nfunction bsearch(arr, target) {\n  let lo = 0, hi = arr.length - 1\n  while (lo \u003C= hi) {\n    const mid = (lo + hi) >> 1\n    if (arr[mid] === target) return mid\n    arr[mid] \u003C target ? (lo = mid + 1) : (hi = mid - 1)\n  }\n  return -1\n}\n```\n\nThe pitfall: the array **must** stay sorted, and `(lo+hi)>>1` assumes\nindices small enough to avoid overflow (fine in practice for arrays).\n",{"id":106,"difficulty":70,"q":107,"a":108},"sort-numbers-strings-mix","What happens if you sort an array with mixed types?","The default sort stringifies everything, giving surprising order; a numeric\ncomparator on mixed types produces `NaN` comparisons that leave order\neffectively undefined.\n\n```js\n[3, '1', 2].sort()              \u002F\u002F ['1', 2, 3] — all compared as strings\n[3, 'x', 2].sort((a,b)=>a-b)    \u002F\u002F 'x'-num is NaN, order unreliable\n```\n\nNormalize types before sorting (e.g. `Number(x)`), or filter out invalid\nentries first. Mixed-type arrays are usually a data-modeling smell.\n",{"id":110,"difficulty":70,"q":111,"a":112},"sort-undefined","How does sort handle undefined and holes?","`sort` moves all **`undefined`** values to the **end** without calling the\ncomparator on them, and **holes** in sparse arrays go after even those.\n\n```js\n[3, undefined, 1].sort((a,b)=>a-b)  \u002F\u002F [1, 3, undefined]\n```\n\nSo you can't reliably place `undefined` via a comparator — the engine\nhandles them specially. Strip or default them beforehand if you need\nspecific placement.\n",{"id":114,"difficulty":14,"q":115,"a":116},"includes-object-reference","Why does includes return false for an object that \"looks\" the same?","`includes` uses identity (SameValueZero), so two distinct objects with the\nsame contents are **not** equal.\n\n```js\nconst arr = [{ id: 1 }]\narr.includes({ id: 1 })           \u002F\u002F false different reference\narr.some(o => o.id === 1)         \u002F\u002F true  compare by value\n```\n\nFor \"contains an object like this,\" use `some`\u002F`find` with a predicate, not\n`includes`\u002F`indexOf`.\n",{"id":118,"difficulty":14,"q":119,"a":120},"tosorted-toreversed","What are toSorted, toReversed, toSpliced, and with?","They are ES2023 **non-mutating** versions of `sort`, `reverse`, `splice`,\nand bracket assignment — each returns a **new array**.\n\n```js\na.toSorted((x,y)=>x-y)   \u002F\u002F sorted copy\na.toReversed()           \u002F\u002F reversed copy\na.toSpliced(1, 2)        \u002F\u002F copy with items removed\u002Finserted\na.with(0, 'new')         \u002F\u002F copy with index 0 replaced\n```\n\nThey make immutable updates concise — especially `with`, which replaces the\nold `[...a.slice(0,i), val, ...a.slice(i+1)]` dance.\n",{"id":122,"difficulty":14,"q":123,"a":124},"search-performance","When should you use a Set or Map instead of array searching?","Repeated `includes`\u002F`indexOf` lookups are **O(n)** each — O(n·m) overall.\nA **`Set`** gives O(1) membership tests.\n\n```js\nconst seen = new Set(bigArray)\nqueries.filter(q => seen.has(q))   \u002F\u002F O(1) per lookup\n```\n\nRule of thumb: if you search the same collection more than a handful of\ntimes, build a `Set`\u002F`Map` index once and query that instead.\n",{"id":126,"difficulty":14,"q":127,"a":128},"dedupe-sorted","How do you remove duplicates, optionally keeping order?","A **`Set`** dedupes and preserves first-seen insertion order in one line.\n\n```js\nconst unique = [...new Set(arr)]   \u002F\u002F order preserved\n```\n\nFor objects (reference equality won't help), dedupe by a key with a `Map`:\n\n```js\nconst byId = [...new Map(users.map(u => [u.id, u])).values()]\n```\n",{"id":130,"difficulty":70,"q":131,"a":132},"sort-comparator-cost","Why can repeated work inside a comparator be a performance problem?","The comparator runs **O(n log n)** times, so any expensive computation\ninside it is repeated constantly. Precompute keys once with a\n**Schwartzian transform**.\n\n```js\nconst sorted = items\n  .map(x => ({ x, key: expensiveKey(x) }))  \u002F\u002F compute once\n  .sort((a, b) => a.key - b.key)\n  .map(o => o.x)                              \u002F\u002F unwrap\n```\n\nThis trades a little memory for far fewer key computations than calling\n`expensiveKey` inside `sort`.\n",{"id":134,"difficulty":25,"q":135,"a":136},"find-default","What does find return when nothing matches, and why be careful?","It returns **`undefined`**. Destructuring or accessing properties on the\nresult without a guard then throws.\n\n```js\nconst u = users.find(u => u.id === 99)\nu.name              \u002F\u002F TypeError if not found\nu?.name             \u002F\u002F optional chaining\nconst { name } = u ?? {}  \u002F\u002F safe default\n```\n\nAlways treat `find`'s result as possibly `undefined`.\n",{"id":138,"difficulty":25,"q":139,"a":140},"indexof-vs-search-string","Does array indexOf relate to string indexOf?","They share a name and a `-1`-on-miss convention, but operate on different\nthings. **Array** `indexOf` matches an element by `===`; **string**\n`indexOf` finds a **substring**.\n\n```js\n['ab', 'cd'].indexOf('b')   \u002F\u002F -1  (no element equals 'b')\n'abcd'.indexOf('b')         \u002F\u002F 1   (substring position)\n```\n\nDon't expect array `indexOf` to do partial\u002Fsubstring matching — use\n`some(s => s.includes('b'))` for that.\n",{"id":142,"difficulty":70,"q":143,"a":144},"sort-locale-numeric","How do you sort strings that contain numbers \"naturally\" (file2 \u003C file10)?","Default and `localeCompare` without options give `file10 \u003C file2`\n(lexicographic). Pass `{ numeric: true }` for **natural sort**.\n\n```js\n['file10','file2'].sort()  \u002F\u002F ['file10','file2']\n['file10','file2'].sort((a,b)=>\n  a.localeCompare(b, undefined, { numeric: true }))\n\u002F\u002F ['file2','file10']\n```\n\nCombine with `sensitivity: 'base'` for case\u002Faccent-insensitive natural\nordering of filenames and version strings.\n",{"id":146,"difficulty":25,"q":147,"a":148},"empty-array-search","What do searching methods return on an empty array?","Consistent, safe defaults: `indexOf`\u002F`findIndex` return `-1`,\n`find` returns `undefined`, `includes`\u002F`some` return `false`, and\n`every` returns `true`.\n\n```js\n[].indexOf('x')   \u002F\u002F -1\n[].find(Boolean)  \u002F\u002F undefined\n[].some(Boolean)  \u002F\u002F false\n[].every(Boolean) \u002F\u002F true   vacuously\n```\n\nThe `every === true` on empty is the one that surprises people in\nvalidation logic — guard for emptiness if \"all valid\" shouldn't pass on no\ndata.\n",null,{"description":11},"JavaScript array searching and sorting interview questions — indexOf, includes, find, the sort() coercion gotcha, numeric comparators, stable sort, toSorted and sorting objects.","javascript\u002Farrays\u002Fsearching-sorting","Searching & Sorting","Arrays & Iteration","arrays","2026-06-18","rwKZ3UXQprDpLSR117P9VFRLTkhBfPapY8I3dIMex0Q",[159,163,166,167],{"subtopic":160,"path":161,"order":162},"Array Methods","\u002Fjavascript\u002Farrays\u002Farray-methods",1,{"subtopic":164,"path":165,"order":12},"Mutating vs Non-Mutating","\u002Fjavascript\u002Farrays\u002Fmutating-vs-nonmutating",{"subtopic":153,"path":21,"order":20},{"subtopic":168,"path":169,"order":170},"Array Destructuring & Spread","\u002Fjavascript\u002Farrays\u002Fdestructuring-spread",4,{"path":172,"title":173},"\u002Fblog\u002Fjavascript-array-searching-sorting","JavaScript Array Searching & Sorting — indexOf, find, the sort() Gotcha and Comparators",1781808676111]