Using java collections binarysearch on Sorted Lists
java collections binarysearch: Learn how to use Collections.binarySearch correctly: sorted-list prerequisites, return value semantics, comparator usage, and performanc...
java collections binarysearch requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The Collections.binarySearch method is a common source of subtle bugs because its contract is stricter than it looks. The method assumes the list is sorted according to the natural ordering of its elements or a supplied comparator. When that assumption holds, it finds an element in O(log n) time. When it does not, the result is undefined and often misleading.
Here is the basic usage:
List<Integer> numbers = new ArrayList<>(List.of(1, 3, 5, 7, 9)); int index = Collections.binarySearch(numbers, 5); System.out.println(index); // 2
The method returns the index of the element if it is present. If the element is absent, it returns a negative value that encodes the insertion point: (-(insertion point) - 1). For example, searching for 6 in the same list returns -4, meaning the insertion point is index 3 (before the 7).
How Collections.binarySearch Works
The method is a static utility in java.util.Collections. There are two overloads:
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
The first overload requires the list elements to implement Comparable. The second accepts a comparator that defines the ordering. Both perform a binary search, halving the search space at each step by comparing the key to the middle element.
Because the method relies on random access to the list, it works efficiently with ArrayList and other RandomAccess implementations. For a linked list, Collections.binarySearch falls back to an iterative binary search that still uses get(int), which degrades to O(n) per access and O(n log n) overall. Prefer ArrayList when binary search is the primary lookup strategy.
The Sorted-List Requirement
The list must be sorted in ascending order according to the same ordering used by the search. If the list is not sorted, the method may return an arbitrary index, including a negative value for an element that is actually present. The Javadoc explicitly states that the behavior is undefined for unsorted lists.
A common mistake is to sort the list with one comparator and search with another. The ordering used for sorting and searching must match exactly. If the list contains objects with a natural ordering that changes over time, the list must be re-sorted before each search.
For example, if you sort a list of strings with String.CASE_INSENSITIVE_ORDER and then call binarySearch without a comparator, the result is unreliable because the default String.compareTo is case-sensitive.
Understanding the Return Value
When the key is found, the return value is the index of the key. If multiple elements equal the key, there is no guarantee which one is returned; the index could be any of the matching positions. This matters when you need the first or last occurrence.
When the key is not found, the return value is (-(insertion point) - 1), where the insertion point is the index of the first element greater than the key, or list.size() if all elements are less than the key. To recover the insertion point from a negative return value:
int insertionPoint = -index - 1;
This formula is useful when you want to insert the key into the list while preserving sorted order:
int index = Collections.binarySearch(sortedList, key); if (index < 0) { sortedList.add(-index - 1, key); }
Using a Comparator for Custom Ordering
When your objects do not implement Comparable, or when you need a non-natural ordering, pass a comparator to the second overload. The comparator must be consistent with the sorting order of the list.
record Person(String name, int age) {} List<Person> people = new ArrayList<>(List.of( new Person("Alice", 30), new Person("Bob", 25), new Person("Carol", 35) )); Comparator<Person> byAge = Comparator.comparingInt(Person::age); people.sort(byAge); int index = Collections.binarySearch(people, new Person("", 25), byAge); System.out.println(index); // 1
Here the comparator defines the ordering, and the list must be sorted with that same comparator. The key does not need to be a full Person; it can be any object of type T that the comparator can compare. In practice, you often create a dummy object with only the field used for comparison.
Performance Characteristics
Binary search runs in O(log n) comparisons, which is significantly faster than linear search for large lists. The actual cost depends on the cost of the comparator and the underlying list's get operation. For ArrayList, get is O(1), so the total is O(log n). For LinkedList, each get is O(n), making the total O(n log n), which is worse than a simple linear scan.
If your list is frequently modified, the cost of keeping it sorted may outweigh the benefit of binary search. Inserting into an ArrayList at an arbitrary position is O(n) because elements shift. If you need both fast search and frequent insertions, consider a TreeSet or TreeMap, which maintain sorted order automatically and offer O(log n) search and insertion.
Common Mistakes and Edge Cases
Duplicate Elements
Binary search does not specify which duplicate index it returns. If you need the first or last occurrence, you must scan backward or forward from the returned index, or use a different data structure like a TreeMap with counts.
Null Elements
If the list contains null and you search for a non-null key, the comparator may throw a NullPointerException depending on how it handles nulls. The natural ordering of most Java types does not allow null, so sorting a list with nulls using Collections.sort will throw. If nulls are allowed, you need a comparator that explicitly handles them.
Unsorted List
As noted, passing an unsorted list leads to undefined results. The method does not check for sortedness. Always sort the list before searching, and be aware that any modification that changes the relative order invalidates the sortedness.
Empty List
An empty list is trivially sorted. binarySearch on an empty list returns -1 for any key, which is the correct insertion point (index 0).
When Not to Use binarySearch
For small lists, a linear search with indexOf is simpler and often faster because the overhead of binary search's loop and comparisons is negligible. The threshold depends on the cost of comparison, but a list of fewer than a dozen elements rarely benefits from binary search.
If your list changes frequently and you search only occasionally, the cost of re-sorting after each modification may dominate. In that case, a HashSet or HashMap provides O(1) lookup without a sorted structure, but loses ordering. Choose binary search when you need the list to remain sorted and search is a frequent operation.
Finally, remember that Collections.binarySearch works on any List, but for ArrayList it is efficient. For a LinkedList, prefer indexOf unless the list is very large and you can convert it to an ArrayList first. The method's contract is clear: the list must be sorted, and the comparator must be consistent. When those conditions are met, it is a reliable and fast way to locate elements in a sorted collection.