Sequential search
Sequential search walks a collection one element at a time. It is the only general option when the data has no order, and it is the baseline every faster search is measured against.
Learning goals
- Trace linear search step by step and count its comparisons
- Explain how a sentinel removes the bounds check from the loop
- State best, average and worst case cost and when each happens
- Search unsorted Java arrays and collections correctly, including objects
Prerequisites
Looking for a name on an unsorted guest list means reading names one by one, because nothing tells you where to jump. That is sequential search.
Linear search
Linear search compares the target with arr[0], then arr[1], and so on. It returns the index of the first match, or -1 if it reaches the end without one.
Invariant: before iteration i, none of arr[0..i-1] equals the target. When the loop ends without returning, the invariant covers the whole array, which proves the target is absent.
Best
O(1)
Average
O(n)
Worst
O(n)
Space
O(1)
Linear search
Interactive · edit the inputint[] arr · length 7
Call linearSearch(arr, 5). Nothing has been checked yet, so start at index 0.
1public static int linearSearch(int[] arr, int target) {2for (int i = 0; i < arr.length; i++) {3if (arr[i] == target) {4return i;5}6}7return -1;8}
Live state
target
5
arr.length
7
Execution trace (dry run table)Show 12 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | target=5 arr.length=7 | Call linearSearch(arr, 5). Nothing has been checked yet, so start at index 0. |
| 2 | L2 | target=5 i=0 | 0 < 7, so the loop body runs for index 0. |
| 3 | L3 | target=5 i=0 arr[i]=7 | 7 != 5. Not a match, move on. |
| 4 | L2 | target=5 i=1 | 1 < 7, so the loop body runs for index 1. |
| 5 | L3 | target=5 i=1 arr[i]=3 | 3 != 5. Not a match, move on. |
| 6 | L2 | target=5 i=2 | 2 < 7, so the loop body runs for index 2. |
| 7 | L3 | target=5 i=2 arr[i]=9 | 9 != 5. Not a match, move on. |
| 8 | L2 | target=5 i=3 | 3 < 7, so the loop body runs for index 3. |
| 9 | L3 | target=5 i=3 arr[i]=14 | 14 != 5. Not a match, move on. |
| 10 | L2 | target=5 i=4 | 4 < 7, so the loop body runs for index 4. |
| 11 | L3 | target=5 i=4 arr[i]=5 | 5 == 5. This is the element we are looking for. |
| 12 | L4 | target=5 i=4 return=4 | return 4. Found after 5 comparisons. |
Sentinel search
A plain linear search does two checks per element: i < n and arr[i] == target. A sentinel search writes the target into the last slot first. The scan is then guaranteed to stop, so the loop only needs the value comparison.
After the loop, restore the original last element and decide whether we stopped on a real match or only on the planted sentinel.
- Saves one comparison per element, which mattered on older hardware and still helps in tight inner loops.
- It temporarily mutates the array, so it is unsafe on shared or read-only data and not thread-safe.
- Modern JIT compilers often remove bounds checks anyway, so measure before choosing it for speed.
Sentinel linear search
Interactive · edit the inputint[] arr · length 6
n = 6. The array is not sorted, so we must scan it.
1public static int sentinelSearch(int[] arr, int target) {2int n = arr.length;3int last = arr[n - 1];4arr[n - 1] = target; // plant the sentinel5int i = 0;6while (arr[i] != target) { // no bounds check needed7i++;8}9arr[n - 1] = last; // restore the array10if (i < n - 1 || last == target) {11return i;12}13return -1;14}
Live state
target
7
n
6
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | target=7 n=6 | n = 6. The array is not sorted, so we must scan it. |
| 2 | L3 | target=7 n=6 last=2 | Save the real last element (2) before we overwrite it. |
| 3 | L4 | target=7 n=6 last=2 | Write the target into the last slot. The scan is now guaranteed to stop, so the loop needs no i < n check. |
| 4 | L5 | target=7 last=2 i=0 | Start scanning at index 0. |
| 5 | L6 | target=7 last=2 i=0 arr[i]=12 | 12 != 7, keep going. One comparison per element instead of two. |
| 6 | L7 | target=7 last=2 i=1 | i++ → 1. |
| 7 | L6 | target=7 last=2 i=1 arr[i]=4 | 4 != 7, keep going. One comparison per element instead of two. |
| 8 | L7 | target=7 last=2 i=2 | i++ → 2. |
| 9 | L6 | target=7 last=2 i=2 arr[i]=19 | 19 != 7, keep going. One comparison per element instead of two. |
| 10 | L7 | target=7 last=2 i=3 | i++ → 3. |
| 11 | L6 | target=7 last=2 i=3 arr[i]=7 | Stopped at index 3. |
| 12 | L9 | target=7 last=2 i=3 | Put 2 back into arr[5] so the caller's array is unchanged. |
| 13 | L10 | target=7 last=2 i=3 | The stop was a real match. |
| 14 | L11 | target=7 return=3 | return 3. |
Best/average/worst case
The cost of linear search depends on where the target is. We count comparisons because they dominate the work.
If the target is present and every position is equally likely, the expected number of comparisons is (1 + 2 + … + n) / n = (n + 1) / 2, which is still O(n).
| Case | When it happens | Comparisons | Big-O |
|---|---|---|---|
| Best | Target is at index 0 | 1 | O(1) |
| Average (present) | Target equally likely anywhere | (n + 1) / 2 | O(n) |
| Worst (present) | Target is the last element | n | O(n) |
| Absent | Target is not in the array | n | O(n) |
Unsorted collection search
With no ordering, sequential search is optimal: any algorithm must look at every element in the worst case, because an unexamined slot could hold the target.
For objects, compare with equals() rather than ==. == only checks whether two references point to the same object. The JDK's List.indexOf and contains are linear searches that use equals().
- Searching many times? Build a
HashSetonce (O(n)) and each lookup becomes O(1) on average. - Data that stays sorted can use binary search instead, at O(log n) per lookup.
1public static <T> int indexOf(T[] items, T target) {2for (int i = 0; i < items.length; i++) {3if (Objects.equals(items[i], target)) { // null-safe equals4return i;5}6}7return -1;8}910List<String> names = List.of("Ana", "Raj", "Lee");11int position = names.indexOf("Raj"); // 1, linear scan using equals()12boolean found = names.contains("Zoe"); // false after n comparisons
Practice ladder
Now try it yourself
Easy
- Return the index of the last occurrence of a target
- Count how many times a value appears
- Find the maximum element and its index
Medium
- Find the first element greater than a given value
- Search a 2D grid row by row and return [row, col]
- Return all indices where the target occurs
Advanced
- Implement sentinel search for String[] using equals()
- Benchmark plain vs sentinel search with JMH
- Find the first non-repeating element in one pass plus a map
Key takeaways
- Linear search is O(n) time and O(1) space, and it works on any order.
- A sentinel removes the per-iteration bounds check at the cost of temporarily mutating the array.
- For objects, compare with equals(); for repeated lookups, switch to hashing or sorting.