Searching

beginner25 min

Check each element until you find what you want.

static int indexOf(int[] values, int target) {
    for (int i = 0; i < values.length; i++) {
        if (values[i] == target) {
            return i;
        }
    }
    return -1;                        // not found
}

Returning -1 for “not found” is the Java convention — String.indexOf and List.indexOf both do it. Whatever you choose, make it impossible to confuse with a valid answer.

Linear search works on any data in any order, and it checks up to every element. For a few thousand items that is instant. It is the right default until you have a reason to do better.

If the data is sorted, you can do much better. Look at the middle. If it is too big, the answer is in the left half; if too small, the right half. Each comparison discards half of what remains.

static int binarySearch(int[] values, int target) {
    int low = 0;
    int high = values.length - 1;

    while (low <= high) {
        int mid = low + (high - low) / 2;

        if (values[mid] == target) {
            return mid;
        } else if (values[mid] < target) {
            low = mid + 1;            // answer is to the right
        } else {
            high = mid - 1;           // answer is to the left
        }
    }
    return -1;
}

A sorted array of a million elements takes about 20 comparisons instead of a million.

Four details that are easy to get wrong

low + (high - low) / 2, not (low + high) / 2. For very large indices the sum can overflow int and go negative. The subtraction form cannot.

while (low <= high), not <. With <, a range that has narrowed to a single element is never examined, so the target is missed whenever it sits in that last position.

mid + 1 and mid - 1, not mid. Assigning low = mid when mid was already ruled out means the range stops shrinking and the loop never ends.

The array must actually be sorted. Binary search on unsorted data returns wrong answers silently — no exception, just an incorrect result.

Those four together are why binary search is famously fiddly. Write it once carefully and test it on: an empty array, one element, the first element, the last element, and a value that is absent.

Use the library

For a plain lookup, Java already has it:

int[] values = {10, 20, 30, 40, 50};
int index = Arrays.binarySearch(values, 30);        // 2

List<String> names = List.of("alice", "bob", "carol");
int i = Collections.binarySearch(names, "bob");     // 1

Both require sorted input. When the value is absent they return a negative number encoding where it would go, which is occasionally useful and often surprising — so check for < 0 rather than == -1.

Finding a boundary

The more valuable use of binary search is not finding an exact value but finding where a condition changes. Suppose a list is sorted and you want the first element at or above a threshold:

// first index where values[index] >= target, or values.length if none
static int lowerBound(int[] values, int target) {
    int low = 0;
    int high = values.length;         // note: length, not length - 1

    while (low < high) {              // note: <, not <=
        int mid = low + (high - low) / 2;

        if (values[mid] < target) {
            low = mid + 1;
        } else {
            high = mid;               // note: mid, not mid - 1
        }
    }
    return low;
}

The three differences from the exact-match version are deliberate and interlocking. This variant never “finds” anything — it narrows until low and high meet at the boundary. Changing one of the three without the others breaks it.

TreeSet gives you the same thing with no code:

TreeSet<Integer> set = new TreeSet<>(List.of(10, 20, 30));
System.out.println(set.ceiling(15));   // 20 — smallest >= 15
System.out.println(set.floor(15));     // 10 — largest <= 15

Hash-based lookup

When you only need “is this present” and order does not matter, a hash set beats both:

Set<Integer> seen = new HashSet<>(List.of(10, 20, 30));
System.out.println(seen.contains(20));   // true, effectively instant

Choosing a search

SituationApproachCost
Unsorted data, one lookupLinear searchProportional to size
Sorted dataBinary searchProportional to log of size
Many lookups, order irrelevantHashSet / HashMapEffectively instant
Need nearest value above or belowTreeSet or lower boundProportional to log of size
Data changes constantlyHashSet, or re-sort if order needed

Sorting to enable binary search is usually not worth it once

Sorting costs more than a single linear scan. Sorting so you can binary search once is slower than just scanning.

It pays off when you search many times against the same data. Sort once, then every subsequent lookup is cheap. If you are doing many lookups and do not need order, a HashSet is simpler and faster still.

Common mistakes

  • Binary search on unsorted data, giving silent wrong answers.
  • (low + high) / 2 overflowing on large arrays.
  • while (low < high) in the exact-match version, missing the final element.
  • low = mid instead of mid + 1, causing an infinite loop.
  • Treating Arrays.binarySearch’s negative return as -1. It encodes an insertion point.
  • Using List.contains in a loop — that is a linear scan each time, turning one pass into a quadratic one.

Practice

  1. Write a linear search returning the index of a target, or -1.
  2. Write binary search from scratch. Test it on an empty array, a single element, the first and last positions, and an absent value.
  3. Write a method returning how many times a value appears in a sorted array, in better than linear time.
  4. Given a sorted array, find the first index whose value is at or above a target.
  5. Given an unsorted array and 1,000 queries asking whether a value is present, choose a structure and justify it.
Hints
  1. Straightforward loop.
  2. The edge cases are the point of the exercise, not the main path.
  3. Two boundary searches: the first index at or above the value, and the first index strictly above it. The difference is the count.
  4. The lowerBound method above. Note it returns values.length when nothing qualifies.
  5. Build a HashSet once, then answer each query instantly. Sorting plus binary search also works but is slower and more code.

Next

Related