Linear Search
Scan elements one by one until the target is found or the input is exhausted.
Overview
Linear search examines each element in order and stops at the first match. It makes no assumptions about the data: unsorted, sorted, duplicates, linked lists, streams — anything that can be iterated can be linearly searched.
It is the baseline every other search is measured against. O(n) sounds slow, but for small n (roughly under 32–64 elements) a tight linear scan often beats Binary Search in wall-clock time because it is branch-predictable and cache-friendly.
Intuition
A mental model before the formal terms.
Looking for your keys by checking every pocket in turn. You do not need your pockets organized; you just check each one until you feel the keys. If they are not in any pocket you only know that after checking all of them.
How it works
- Start at index
0. - Compare
a[i]withtarget. If equal, returni. - Otherwise advance
iby one and repeat. - If
ireachesnwithout a match, return-1(not found).
Why it works
Correctness is immediate: every index is visited, so if the target exists it is compared at some point and the first match is returned.
Termination: i strictly increases and is bounded by n, so the loop runs at most n times.
Recognition
How to tell a problem wants this.
- The input is unsorted and searched only once — sorting first would cost more than the scan.
- You need the first occurrence in original order, or you must inspect every element anyway (e.g. count matches, find min/max).
- The container has no random access (linked list, iterator, stream), so index-halving strategies do not help.
Interactive visualization
Play, step, change the input. ← → and space work too.
1for i in 0 .. n-1:2 if a[i] == target: return i3return -1Pseudocode
1for i from 0 to n - 1:2 if a[i] == target: return i3return -1Implementations
1from typing import Callable, Optional, Sequence, TypeVar2 3T = TypeVar("T")4 5 61 · The baseline: look at every element until one matches7def linear_search(a: Sequence[T], target: T) -> int:8 for i, value in enumerate(a):9 if value == target:10 return i11 return -112 13 142 · The standard-library form, which works on any predicate15def find_first_negative(a: Sequence[int]) -> int:16 return next((i for i, x in enumerate(a) if x < 0), -1)17 18 193 · A sentinel removes the bounds check from the inner loop20def sentinel_search(a: list[int], target: int) -> int:21 if not a:22 return -123 last = a[-1]24 a[-1] = target # guarantees a match, so no i < n test is needed25 i = 026 while a[i] != target:27 i += 128 a[-1] = last29 return i if i < len(a) - 1 or last == target else -130 31 324 · Unsorted data leaves no choice: every miss costs a full scan33def contains(names: Sequence[str], name: str) -> bool:34 return name in namesenumerate(a)yields index/value pairs, which is the Pythonic replacement forrange(len(a))plus indexing.next((i for i, x in enumerate(a) if x < 0), -1)is the one-expression "first index matching a predicate" — the generator is lazy, so it stops at the first hit.sentinel_searchmutates the list and restores it; the final conditional distinguishes a genuine hit from stopping on the planted sentinel.name in namesdispatches to__contains__, which for alistis a linear scan and for asetordictis a hash lookup — same syntax, wildly different cost.==in Python is structural for built-in types, solinear_searchfinds an equal tuple or list, unlike the===versions in JS/TS.
in and list.index run their loop in C and are several times faster than an equivalent Python-level for, at the same O(n).
list.index(x)returns the index but raisesValueErroron a miss instead of returning -1 — wrap it intry/exceptor use thenext(..., -1)idiom.inon asetordictis O(1) average; on alistortupleit is O(n). Swapping the container is the entire optimisation.==compares by value for built-ins and by__eq__for custom classes;iscompares identity and is only correct for singletons likeNone.bisect.bisect_leftis the sorted-input answer, andoperator.countOf(a, x)counts occurrences in C.
- Using
list.indexwithout catchingValueError, which turns a normal miss into an exception at runtime. - Writing
if name is targetfor strings — it works for short interned literals and fails for computed strings, which is the worst kind of bug. - Calling
x in big_listinside a loop instead of building asetonce, which is the classic accidental O(n²).
- Miss convention: C++ returns
end(), Pythonlist.indexraisesValueError, and JS/TS return -1 — three different contracts for the same event. - Equality: Python
==is structural (a list equals an equal list), JS/TS===is reference identity for objects, and C++operator==is whatever the type defines. - The idiomatic call differs:
std::find_ifwith a lambda,Array.prototype.findIndex, andnext((i for ...), -1)— all three express "first index matching a predicate" in one line. - Sparse arrays exist only in JS/TS, where
indexOfskips holes; C++ vectors and Python lists have no holes to skip.
Complexity
Average is n/2 comparisons for a present target, n for an absent one.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Unsorted data queried once or a handful of times.
- Very small arrays (tens of elements) where constant factors dominate.
- Sequential containers without random access: linked lists, iterators, file streams.
- When the match predicate is arbitrary and no ordering of the data corresponds to it.
- Sorted data with repeated queries — use Binary Search for
O(log n). - Many membership queries on a static set — build a Hash Set for
O(1)average lookups. - Large inputs (
n ≥ 10^6) queried repeatedly; theO(n·q)total is too slow.
Alternatives
Common mistakes
- Returning the last match instead of the first when the problem asks for the first index.
- Forgetting the not-found case and returning
norundefined. - Using linear search inside a loop over another large array, producing
O(n·m)when a hash set givesO(n + m).
Interview patterns
- Sentinel search: place the target at
a[n]to drop the bounds check from the inner loop. - Find the first element satisfying an arbitrary predicate (
find,findIndex,any). - Single pass to compute min/max/second-largest — the same scan structure.
- Explain why a hash set or sorting is the improvement, then implement that.
- Recognizing the approach from an array and a targetIntermediate
- When space complexity mattersIntermediate
- Two pointers or hash map?Intermediate
- Convincing me your algorithm is correctExpert
- Two SumBeginner