DSALinear Search

Linear Search

Linear search is the simplest searching algorithm: scan through the collection from start to finish until you find the target or exhaust all elements. It requires no preprocessing, no sorting, and works on any data structure that can be iterated.

Despite being "just" O(n), linear search is frequently the right choice — and occasionally the only choice. Understanding when and why to use it (and when not to) is important.

The Basic Algorithm

JS
// Returns the index of target in arr, or -1 if not found
function linearSearch(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) return i;
  }
  return -1;
}

// Usage
const arr = [64, 34, 25, 12, 22, 11, 90];
linearSearch(arr, 25);   // → 2  (found at index 2)
linearSearch(arr, 99);   // → -1 (not found)

// Complexity:
// Best case:  O(1) — target is the first element
// Worst case: O(n) — target is last, or not present
// Average:    O(n/2) = O(n) — expected to check half the array
// Space:      O(1) — only two variables (i and comparison result)
Sentinel Optimization

A classic micro-optimization: place the target value at the END of the array as a "sentinel." This eliminates the bounds check (i < arr.length) from every loop iteration — cutting the number of comparisons per step in half. The loop is guaranteed to terminate because it will always find the sentinel, so no out-of-bounds check is needed.

JS
// Standard linear search: 2 comparisons per iteration (i < n AND arr[i] === target)
function linearSearchStandard(arr, target) {
  for (let i = 0; i < arr.length; i++) {   // comparison 1: bounds check
    if (arr[i] === target) return i;         // comparison 2: value check
  }
  return -1;
}

// Sentinel optimization: 1 comparison per iteration (only value check)
function linearSearchSentinel(arr, target) {
  const n = arr.length;
  const last = arr[n - 1];    // save the last element
  arr[n - 1] = target;        // place sentinel at end

  let i = 0;
  while (arr[i] !== target) { // only ONE comparison per iteration!
    i++;
  }

  arr[n - 1] = last;          // restore the last element

  if (i < n - 1 || last === target) return i;
  return -1;  // only found the sentinel, not actual target
}

// In practice: JavaScript's JIT compiler often makes this unnecessary.
// The optimization is more valuable in C/C++ or inner loops of critical code.
// Conceptually: it demonstrates that bounds checks have a cost.
When Linear Search Is the Right Choice
  • Unsorted data — you cannot use binary search without sorting first

  • One-time search — sorting costs O(n log n), linear search costs O(n)

  • Very small arrays (n < ~20) — binary search overhead makes linear faster in practice

  • Linked lists — random access is O(n) anyway, binary search gives no benefit

  • Searching for a custom condition — not a simple equality check (e.g., find first element > 42)

  • Streaming data — you receive elements one by one and cannot pre-sort

Find First, Last, and All Occurrences

JS
// Find the FIRST occurrence (standard)
function findFirst(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) return i;
  }
  return -1;
}

// Find the LAST occurrence — scan from the end
function findLast(arr, target) {
  for (let i = arr.length - 1; i >= 0; i--) {
    if (arr[i] === target) return i;
  }
  return -1;
}

// Find ALL occurrences — collect every matching index
function findAll(arr, target) {
  const indices = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) indices.push(i);
  }
  return indices;
}

// Find by CONDITION — more powerful than equality check
function findFirstWhere(arr, predicate) {
  for (let i = 0; i < arr.length; i++) {
    if (predicate(arr[i])) return i;
  }
  return -1;
}

// Example: find first negative number
const nums = [3, 7, -2, 5, -8, 1];
findFirstWhere(nums, x => x < 0);   // → 2 (index of -2)

// JavaScript's built-in equivalents (all use linear scan internally):
nums.indexOf(-2);           // find first: O(n)
nums.lastIndexOf(-2);       // find last: O(n)
nums.findIndex(x => x < 0); // find first by condition: O(n)
nums.filter(x => x < 0);    // find all by condition: O(n), returns [-2, -8]
Searching in a Linked List

Linked lists do not support random access — you cannot jump to index i in O(1) like arrays. This makes binary search impossible on a plain linked list. Linear search is the only option.

JS
class ListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

// Build list: 1 -> 3 -> 5 -> 7 -> 9
const head = new ListNode(1,
  new ListNode(3,
    new ListNode(5,
      new ListNode(7,
        new ListNode(9)))));

// Linear search on linked list: O(n)
function searchLinkedList(head, target) {
  let current = head;
  let index = 0;
  while (current !== null) {
    if (current.val === target) return index;
    current = current.next;
    index++;
  }
  return -1;
}

searchLinkedList(head, 5);   // → 2 (third node)
searchLinkedList(head, 4);   // → -1 (not found)

// Find node by condition
function findNode(head, predicate) {
  let current = head;
  while (current !== null) {
    if (predicate(current.val)) return current;
    current = current.next;
  }
  return null;
}

// Find the middle of a linked list (fast-slow pointer — a form of linear scan)
function findMiddle(head) {
  let slow = head, fast = head;
  while (fast !== null && fast.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;  // slow is at the middle when fast reaches the end
}
Linear Search on 2D Arrays

JS
// Search in a 2D matrix (unsorted)
function searchMatrix2D(matrix, target) {
  for (let r = 0; r < matrix.length; r++) {
    for (let c = 0; c < matrix[r].length; c++) {
      if (matrix[r][c] === target) return [r, c];
    }
  }
  return [-1, -1];
}

const matrix = [
  [1,  3,  5],
  [7,  9,  11],
  [13, 15, 17],
];
searchMatrix2D(matrix, 9);   // → [1, 1]
searchMatrix2D(matrix, 4);   // → [-1, -1]

// Flatten and search (useful with functional style)
function searchMatrixFlat(matrix, target) {
  return matrix.flat().indexOf(target);  // returns flat index
}
LeetCode-Style Problems

Problem: Find the Target in a Rotated Unsorted Array Given an array that has been shuffled (not sorted), find a target value. Classic linear scan.

JS
// LeetCode 1 — Two Sum (uses linear search with hash map)
// Given nums and target, find two indices that sum to target.
function twoSum(nums, target) {
  const seen = new Map();  // value → index
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (seen.has(complement)) return [seen.get(complement), i];
    seen.set(nums[i], i);
  }
  return [];
}
// O(n) time, O(n) space — one pass linear scan with hash lookup

// Find minimum in unsorted array (linear search for minimum)
function findMin(nums) {
  let min = nums[0];
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] < min) min = nums[i];
  }
  return min;
}

// LeetCode 485 — Max Consecutive Ones
// Find the maximum number of consecutive 1s in a binary array
function findMaxConsecutiveOnes(nums) {
  let maxCount = 0, count = 0;
  for (const num of nums) {
    if (num === 1) { count++; maxCount = Math.max(maxCount, count); }
    else count = 0;
  }
  return maxCount;
}
// findMaxConsecutiveOnes([1,1,0,1,1,1]) → 3
findMaxConsecutiveOnes([1,1,0,1,1,1]) → 3
findMin([3,1,4,1,5,9,2,6]) → 1
twoSum([2,7,11,15], 9) → [0, 1]
Optimizing Linear Search

When linear search is unavoidable, there are practical ways to reduce the constant factor:

JS
// 1. Move-to-front heuristic: if an element is found, move it to the front.
//    Frequently accessed elements accumulate near the start → faster future searches.
function linearSearchMTF(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) {
      if (i > 0) [arr[0], arr[i]] = [arr[i], arr[0]];  // move to front
      return 0;
    }
  }
  return -1;
}

// 2. Frequency ordering: sort elements by access frequency.
//    Accessed often? Near the front. Like a cache.

// 3. Early termination with condition:
//    If you know elements are sorted and target < current, stop early.
function linearSearchSorted(arr, target) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === target) return i;
    if (arr[i] > target) return -1;  // early exit — target cannot appear later
  }
  return -1;
}
Tip
JavaScript arrays are objects under the hood. For very large arrays (tens of millions of elements), prefer typed arrays (Int32Array, Float64Array) — they store values as contiguous memory without boxing, giving faster cache-friendly linear scans.
Note
Linear search is not just for arrays. Any time you iterate a collection sequentially (for...of, forEach, map, filter, find), you are performing a linear scan. All of these are O(n). There is nothing wrong with that — O(n) is optimal when the data is unsorted.