Skip to main content

Linear Search

Overviewโ€‹

Linear search examines each element in turn until it finds what it is looking for or runs out. It is the only search that works on any sequence with no preconditions whatsoever โ€” unsorted, singly linked, streamed, or generated lazily.

Its reputation as the naive option is only half deserved. On small inputs it is genuinely the fastest option available, for reasons that have nothing to do with complexity.

Core Conceptsโ€‹

PropertyValue
Best caseO(1) โ€” the target is first
AverageO(n/2) โ†’ O(n)
Worst caseO(n) โ€” last, or absent
SpaceO(1)
RequiresNothing โ€” not even random access
Works onArrays, linked lists, streams, generators, any iterable

Architecture / Mechanismโ€‹

def linear_search(items, target):
for i, x in enumerate(items):
if x == target:
return i
return -1

There is nothing more to the algorithm. What is worth noting is the generalisation: because it never assumes an ordering, the comparison can be any predicate, not just equality.

# Binary search cannot do this โ€” there is no ordering to exploit
first_error = next((r for r in records if r.status >= 500), None)

That is the real dividing line between the two searches. Binary search needs a property that partitions the sequence monotonically; linear search needs nothing.

Practical Usageโ€‹

Linear search wins on small arrays, and by more than you would expect

Below roughly 50โ€“100 elements, a linear scan typically beats a binary search on real hardware. Three reasons, none of them visible in the complexity:

  • Sequential access. The scan walks contiguous memory, so the prefetcher has the next cache line ready before it is asked. Binary search jumps to the middle, then a quarter, then an eighth โ€” each a likely cache miss.
  • Branch prediction. The loop's "keep going" branch is taken almost every iteration and predicts nearly perfectly. Binary search's "go left or right" is close to random and mispredicts about half the time, costing 10โ€“20 cycles each.
  • No setup. No index arithmetic, no bounds juggling.

This is exactly why hybrid sorts fall back to insertion sort at small sizes, and why hash table implementations scan short chains rather than indexing them.

Where linear search is the right choice regardless of size:

  • The data is unsorted and used once. Sorting to search once is strictly worse.
  • The predicate is not an ordering. "First record matching this regex" has no sorted form.
  • The sequence is not random-access โ€” a linked list or a stream. Binary search on a linked list would cost O(n) per probe, making it worse than scanning.
  • The data does not exist yet. Searching a generator or a network stream as it arrives.
# Sentinel search: remove the bounds check by guaranteeing a match
def sentinel_search(a, target):
last = a[-1]
a[-1] = target # the scan is now guaranteed to terminate
i = 0
while a[i] != target:
i += 1
a[-1] = last
return i if i < len(a) - 1 or last == target else -1

The sentinel trick removes one comparison per iteration. It is a genuine micro-optimisation in tight C loops and a curiosity everywhere else โ€” modern compilers and branch predictors have largely erased the gain, and it mutates the array, which rules it out for shared or immutable data.

Edge Cases & Pitfallsโ€‹

  • Returning 0 for "not found" collides with a valid index. Return -1, None, or an optional type โ€” and be consistent across the codebase.
  • Repeated linear searches inside a loop silently make the enclosing algorithm O(nยฒ). This is the single most common cause of accidental quadratic behaviour, and the fix is almost always a hash table โ€” see the two_sum example there.
  • in on a list is O(n); on a set or dict it is O(1). In Python they look identical at the call site, which is what makes the mistake easy.
  • Duplicates. Decide whether you want the first match, the last, or all of them.

Comparisonsโ€‹

LinearBinaryHash
Per lookupO(n)O(log n)O(1) expected
PreparationNoneSort: O(n log n)Build: O(n)
Requires sorted dataNoYesNo
Requires random accessNoYesNo
Arbitrary predicatesYesOnly monotonic onesExact keys only
Best forSmall, unsorted, or one-shotMany lookups, static sorted dataMany lookups by exact key

Referencesโ€‹

  • Knuth, The Art of Computer Programming, Vol. 3, ยง6.1 โ€” "Sequential Searching", including the sentinel variant and its analysis.
  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms โ€” linear search appears as Exercise 2.1-3, with its loop invariant.
  • Binary Search โ€” the logarithmic alternative, and what it demands in return.
  • Hash Tables โ€” the usual fix when a linear search sits inside a loop.
  • CPU Caches โ€” why the constant factors favour scanning at small sizes.