Searching and Sorting Arrays

โ–ถ Try It Yourself

Finding and ordering data are two of the most common operations in any application โ€” displaying a leaderboard, filtering a product list, locating a user record, or sorting search results. JavaScript provides a comprehensive set of methods for both. In this lesson you will master the full array search toolkit (find, findIndex, indexOf, includes, some, every) and the sort method with custom comparators for sorting numbers, strings, dates, and multi-column data.

Search Methods Comparison

Method Returns Best For
includes(val) Boolean “Does this value exist?” โ€” primitives only
indexOf(val) First index or -1 Find position of primitive value
lastIndexOf(val) Last index or -1 Find last occurrence of primitive value
find(fn) First matching element or undefined Find one object by condition
findIndex(fn) Index of first match or -1 Find position of object by condition
findLast(fn) Last matching element Find last match (ES2023)
some(fn) Boolean โ€” true if any match “Does any element satisfy this?”
every(fn) Boolean โ€” true if all match “Do all elements satisfy this?”
filter(fn) New array of all matches Get all matching elements

sort() Comparator Rules

Return Value Meaning Result
Negative number a comes before b a, b
0 a and b are equal Order unchanged
Positive number b comes before a b, a
a - b Numbers ascending Lowest first
b - a Numbers descending Highest first
a.localeCompare(b) Strings ascending Alphabetical Aโ†’Z

Default Sort Behaviour โ€” The Common Gotcha

Input Default sort() Correct sort
[10, 9, 2, 21, 100] [10, 100, 2, 21, 9] โ€” lexicographic! [2, 9, 10, 21, 100] with (a,b) => a-b
['banana','apple','cherry'] ['apple','banana','cherry'] โ€” works Use localeCompare for accented chars
Array of objects Converts to string โ€” breaks completely Always provide comparator for objects
Note: find and findIndex stop at the first match โ€” they short-circuit like &&. If you need all matching elements, use filter. If you only need to know whether a match exists, use some โ€” it is more semantically clear than checking find(fn) !== undefined.
Tip: For stable multi-column sorting โ€” sorting by one field, then by another as a tiebreaker โ€” chain comparisons with ||: (a, b) => a.dept.localeCompare(b.dept) || a.name.localeCompare(b.name). The first comparison only returns 0 (falsy) when the departments are equal, at which point the second comparison determines the order.
Warning: JavaScript’s default sort() with no comparator converts elements to strings and sorts them lexicographically. This means [10, 9, 2, 21].sort() gives [10, 2, 21, 9] โ€” because '10' < '2' alphabetically. Always provide a numeric comparator: .sort((a, b) => a - b). This is one of the most common JavaScript gotchas.

Basic Example

const users = [
    { id: 1, name: 'Alice',  age: 30, dept: 'Engineering', salary: 95000, active: true  },
    { id: 2, name: 'Bob',    age: 25, dept: 'Marketing',   salary: 72000, active: true  },
    { id: 3, name: 'Carol',  age: 35, dept: 'Engineering', salary: 88000, active: false },
    { id: 4, name: 'Dave',   age: 28, dept: 'Engineering', salary: 102000, active: true },
    { id: 5, name: 'Eve',    age: 25, dept: 'Marketing',   salary: 68000, active: true  },
];

// โ”€โ”€ find โ€” first object match โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const alice = users.find(u => u.name === 'Alice');
console.log(alice?.name);   // 'Alice'

const senior = users.find(u => u.age >= 35);
console.log(senior?.name);  // 'Carol'

// โ”€โ”€ findIndex โ€” position of object โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const idx = users.findIndex(u => u.id === 3);
console.log(idx);   // 2

// โ”€โ”€ some / every โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const anyInactive  = users.some(u => !u.active);          // true (Carol)
const allEngineers = users.every(u => u.dept === 'Engineering'); // false
const allEarning   = users.every(u => u.salary > 50000);  // true

console.log(anyInactive, allEngineers, allEarning);

// โ”€โ”€ includes vs find โ€” primitives vs objects โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const ids = [1, 2, 3, 4, 5];
console.log(ids.includes(3));             // true  โ€” works for primitives
console.log(users.includes({ id: 1 }));   // false โ€” objects compared by reference

// โ”€โ”€ SORT โ€” always copy first โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Numbers ascending
const byAgeAsc  = [...users].sort((a, b) => a.age - b.age);
// Numbers descending
const bySalDes  = [...users].sort((a, b) => b.salary - a.salary);
// Strings alphabetical
const byName    = [...users].sort((a, b) => a.name.localeCompare(b.name));
// Boolean โ€” active first
const actFirst  = [...users].sort((a, b) => Number(b.active) - Number(a.active));

console.log(byAgeAsc.map(u => `${u.name}(${u.age})`));
// ['Bob(25)','Eve(25)','Dave(28)','Alice(30)','Carol(35)']

console.log(bySalDes.map(u => `${u.name}:$${u.salary}`));
// ['Dave:$102000','Alice:$95000','Carol:$88000','Bob:$72000','Eve:$68000']

// โ”€โ”€ Multi-column sort โ€” dept asc, then salary desc โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const multiSort = [...users].sort((a, b) =>
    a.dept.localeCompare(b.dept) ||   // primary: dept A-Z
    b.salary - a.salary               // tiebreaker: salary highest first
);
console.log(multiSort.map(u => `${u.dept}/${u.name}/$${u.salary}`));
// Engineering/Dave/$102000
// Engineering/Alice/$95000
// Engineering/Carol/$88000
// Marketing/Bob/$72000
// Marketing/Eve/$68000

// โ”€โ”€ Binary search on sorted array (manual) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function binarySearch(sortedArr, target) {
    let low = 0, high = sortedArr.length - 1;
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        if (sortedArr[mid] === target) return mid;
        if (sortedArr[mid] < target)  low  = mid + 1;
        else                           high = mid - 1;
    }
    return -1;
}

const sorted = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91];
console.log(binarySearch(sorted, 23));   // 5
console.log(binarySearch(sorted, 99));   // -1

How It Works

Step 1 โ€” find Short-Circuits on First Match

find iterates the array and calls the callback for each element. The moment the callback returns truthy, find returns that element and stops iterating. This makes it efficient for large arrays โ€” no need to check every element once a match is found.

Step 2 โ€” some and every Also Short-Circuit

some stops and returns true as soon as one element passes. every stops and returns false as soon as one element fails. For large arrays this is significantly faster than checking all elements with filter().length > 0.

Step 3 โ€” sort() Uses an In-Place Comparison Sort

JavaScript’s sort uses an internal algorithm (TimSort in V8) that calls your comparator function repeatedly with pairs of elements. The comparator’s return value tells sort which element should come first. Always copy the array with [...arr] before sorting if you need the original order preserved.

Step 4 โ€” Multi-Column Sort with ||

The || in a.dept.localeCompare(b.dept) || b.salary - a.salary exploits short-circuit evaluation. When departments differ, localeCompare returns a non-zero number (truthy) and || returns that value โ€” the salary comparison is never evaluated. When departments are equal, localeCompare returns 0 (falsy) and || evaluates the right side โ€” the salary comparison breaks the tie.

Step 5 โ€” Binary Search for Sorted Arrays

Linear search (find, indexOf) is O(n) โ€” it checks every element in the worst case. Binary search is O(log n) โ€” it halves the search space each iteration. For a sorted array of 1,000,000 elements, linear search may check 1,000,000 elements; binary search checks at most 20. The tradeoff is the array must already be sorted.

Real-World Example: Product Search and Sort UI

// product-search.js

const products = [
    { id: 1, name: 'Laptop',      price: 999,  category: 'tech',     rating: 4.5, inStock: true  },
    { id: 2, name: 'Headphones',  price: 149,  category: 'tech',     rating: 4.8, inStock: true  },
    { id: 3, name: 'Coffee Mug',  price: 15,   category: 'kitchen',  rating: 4.2, inStock: false },
    { id: 4, name: 'Desk Chair',  price: 350,  category: 'furniture',rating: 4.6, inStock: true  },
    { id: 5, name: 'Notebook',    price: 8,    category: 'office',   rating: 4.0, inStock: true  },
    { id: 6, name: 'Monitor',     price: 450,  category: 'tech',     rating: 4.7, inStock: true  },
];

function searchProducts({ query = '', category = null, maxPrice = Infinity,
                           inStockOnly = false, sortBy = 'name', sortDir = 'asc' } = {}) {
    const q = query.toLowerCase().trim();

    const filtered = products.filter(p => {
        if (q && !p.name.toLowerCase().includes(q)) return false;
        if (category && p.category !== category)       return false;
        if (p.price > maxPrice)                          return false;
        if (inStockOnly && !p.inStock)                  return false;
        return true;
    });

    const comparators = {
        name:   (a, b) => a.name.localeCompare(b.name),
        price:  (a, b) => a.price - b.price,
        rating: (a, b) => a.rating - b.rating,
    };

    const cmp = comparators[sortBy] ?? comparators.name;
    filtered.sort((a, b) => sortDir === 'desc' ? cmp(b, a) : cmp(a, b));

    return filtered;
}

console.log(searchProducts({ category: 'tech', sortBy: 'price', sortDir: 'desc' }));
// Monitor $450, Laptop $999, Headphones $149 โ€” sorted price desc

console.log(searchProducts({ maxPrice: 200, inStockOnly: true, sortBy: 'rating', sortDir: 'desc' }));
// Headphones(4.8), Notebook(4.0) โ€” in-stock, under $200, best-rated first

Common Mistakes

Mistake 1 โ€” Sorting numbers without a comparator

โŒ Wrong โ€” default sort is lexicographic:

[100, 20, 3].sort()           // [100, 20, 3] โ†’ '100' < '20' < '3'
// Result: [100, 20, 3] โ€” sorted as strings!

โœ… Correct โ€” provide a numeric comparator:

[100, 20, 3].sort((a, b) => a - b)   // [3, 20, 100]

Mistake 2 โ€” Using find when all matches are needed

โŒ Wrong โ€” find only returns the first match:

const activeUser = users.find(u => u.active);   // only the FIRST active user

โœ… Correct โ€” use filter to get all matches:

const activeUsers = users.filter(u => u.active);  // ALL active users

Mistake 3 โ€” Forgetting sort mutates the original array

โŒ Wrong โ€” sort modifies the original:

const sorted = items.sort((a, b) => a.price - b.price);
// Both items AND sorted now point to the same mutated array

โœ… Correct โ€” copy before sorting:

const sorted = [...items].sort((a, b) => a.price - b.price);
// items is unchanged; sorted is a new sorted array

▶ Try It Yourself

Quick Reference

Goal Method Returns
Does value exist? includes(val) Boolean
Find first object match find(fn) Element or undefined
Find position findIndex(fn) Index or -1
Any match? some(fn) Boolean
All match? every(fn) Boolean
Sort numbers asc [...arr].sort((a,b) => a-b) Sorted copy
Sort strings [...arr].sort((a,b) => a.localeCompare(b)) Sorted copy
Multi-column sort .sort((a,b) => cmp1(a,b) || cmp2(a,b)) Sorted copy

🧠 Test Yourself

What does [10, 9, 2, 21].sort() return?





โ–ถ Try It Yourself