Binary Search





Binary Search – Super Simple Guide


Imagine you have a big list of numbers, like phone numbers or prices, and you want to find one number very fast. If you check one number after another from the start to the end, it can take a long time, especially when the list is huge. But if the list is already in order, you can do something much smarter called binary search. This method cuts the search space in half again and again, which makes it very quick. In this article, we will learn how binary search works in super simple English, so you can understand it even if you are new to coding. We will explain the big idea, show step-by-step examples, and share easy-to-read Java code. We will also use friendly analogies, like a guessing game and a dictionary, so the idea feels natural. You will learn typical mistakes, and you will also learn when to use it and when not to use it. By the end, you will know why sorted data matters, what the left, right, and mid positions mean, and how to avoid errors like off-by-one mistakes or overflow. Keep reading, and you will see that this powerful idea is simple, clean, and very practical in real life and in coding interviews too.

Binary Search: A Super Simple Guide

What Is Binary Search?

Binary search is a fast way to find a target value in a sorted list. The key word here is sorted. If the list is not in order, binary search will not work correctly. The main idea is simple: you look at the number in the middle first. If the middle number is the one you want, you are done. If your target is smaller than the middle number, you only need to search the left half. If your target is larger, you only need to search the right half. Each time, you throw away half of the list, so the number of checks becomes very small, even for very large lists. Think about a list with one million items. A simple check-one-by-one method might need up to one million steps. But binary search will only need about 20 steps, because it keeps cutting the search area in half. This is why binary search is famous for its O(log n) time complexity. In code, we usually keep two pointers or indexes called left and right, and we compute a mid index between them. We compare the value at mid with our target, and then we move left or right to shrink the search range until we find the target or the range becomes empty.

Why Do We Need a Sorted Array?

A sorted array means all the numbers are in order, usually from small to big. This order tells us something very powerful when we make a middle guess. If we look at the mid value and it is bigger than our target, we instantly know that the target cannot be anywhere to the right of mid, because everything to the right is even bigger. So we throw away the right half. If the mid value is smaller than our target, the target cannot be anywhere to the left of mid, because everything on the left is even smaller. So we throw away the left half. Without sorting, we do not have this reliable rule, so throwing away half would be risky and could skip the answer. Sorting gives us structure, and structure gives us speed. This is why it is common to sort the data first, and then use binary search many times after that. While sorting has a cost, if you will search many times, sorting once can be worth it. In real systems, we often store data in sorted order for faster search, just like a library organizes books by category and author to help us find them quickly and without guesswork.

How Binary Search Works (Big Picture)

Think about a guessing game where your friend picks a number between 1 and 100, and you want to guess it in as few tries as possible. The smartest first guess is 50, the middle. If your friend says the number is higher, you only look at 51 to 100. If your friend says lower, you only look at 1 to 49. With each answer, your search area halves. This is the exact idea behind binary search. Another helpful analogy is a dictionary. If you are looking up a word that starts with “S,” you do not flip one page at a time. You open near the middle. If you see “M,” you know your word is later, so you jump forward. If you see “W,” you jump back. In code, you do the same using numbers: keep a left index for the start and a right index for the end, then compute a mid position. Compare the value at mid with your target. Decide which half to keep, and repeat. The loop ends when you find the target or when left goes past right, which means the value is not in the list. This approach is clean, logical, and fast, because each step gives you a lot of information and cuts away a big part of the search space.

Step-by-Step Example (Numbers)

Let us try a clear example. Suppose we have a sorted array: [2, 5, 7, 9, 12, 15, 18, 21], and we want to find the target 12. We will follow the binary search flow, just like the guessing game. We keep two indexes: left = 0 (first index) and right = 7 (last index), because the array has eight items. We compute mid as the middle index using the safe formula mid = left + (right – left) / 2. We compare the number at mid with our target each time to decide whether to go left or right. We stop when we find the target or when the search area becomes empty. The steps below show the exact moves, so you can see how the range shrinks with each decision. After reading this example, try changing the target to 21 or 2 or 10 and repeat the steps. You will see how clean and predictable the method is, and you will also learn to update left, right, and mid without confusion. This practice will make the code feel natural and reduce mistakes later.

  1. Start: left = 0, right = 7. Compute mid = 0 + (7 – 0) / 2 = 3. Value at mid is 9.
  2. Compare 9 with target 12. 9 is smaller, so the target must be on the right side. Set left = mid + 1 = 4.
  3. Now: left = 4, right = 7. Compute mid = 4 + (7 – 4) / 2 = 5. Value at mid is 15.
  4. Compare 15 with target 12. 15 is larger, so the target must be on the left side. Set right = mid – 1 = 4.
  5. Now: left = 4, right = 4. Compute mid = 4 + (4 – 4) / 2 = 4. Value at mid is 12.
  6. Compare 12 with target 12. It matches! We return the index 4. Search is done.

Java Code Examples

Below are two common versions of binary search in Java: an iterative version that uses a loop, and a recursive version that calls itself with smaller ranges. Both do the same logical steps. We keep left and right as the current search range, compute mid, compare, and then move left or right. A very important detail is how we compute mid. We use left + (right – left) / 2 instead of (left + right) / 2. This avoids integer overflow when numbers are very large. Also remember the loop condition left <= right, because when left goes past right, the range is empty. If we exit the loop without finding the target, we return -1 to say “not found.” For the recursive method, we check a base case first: if left > right, the value is absent. Then we do the same mid check and move into the left half or right half by calling the function again with updated indices. Try to run both versions with different inputs so the control flow feels easy and natural to you.

Iterative Version


public class BinarySearchDemo {
    // Returns index of target if found, else -1
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;     // search right half
            } else {
                right = mid - 1;    // search left half
            }
        }
        return -1; // not found
    }

    public static void main(String[] args) {
        int[] nums = {2, 5, 7, 9, 12, 15, 18, 21};
        int index = binarySearch(nums, 12);
        System.out.println(index); // 4
    }
}
    
  1. Initialize left = 0 and right = n – 1.
  2. While left <= right, compute mid = left + (right - left) / 2.
  3. If arr[mid] equals target, return mid.
  4. If arr[mid] is less than target, set left = mid + 1 to search the right half.
  5. Else, set right = mid – 1 to search the left half.
  6. If the loop ends, return -1 because the value is not in the array.

Recursive Version


public class BinarySearchRecursiveDemo {
    public static int binarySearchRecursive(int[] arr, int target) {
        return search(arr, 0, arr.length - 1, target);
    }

    private static int search(int[] arr, int left, int right, int target) {
        if (left > right) {
            return -1; // base case: not found
        }

        int mid = left + (right - left) / 2;

        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            return search(arr, mid + 1, right, target);
        } else {
            return search(arr, left, mid - 1, target);
        }
    }

    public static void main(String[] args) {
        int[] nums = {2, 5, 7, 9, 12, 15, 18, 21};
        int index = binarySearchRecursive(nums, 21);
        System.out.println(index); // 7
    }
}
    
  1. Call search with the full range (0 to n – 1).
  2. If left > right, stop and return -1.
  3. Find mid safely using left + (right – left) / 2.
  4. If arr[mid] equals target, return mid.
  5. If arr[mid] < target, recurse into the right half (mid + 1 to right).
  6. Else recurse into the left half (left to mid – 1).

Time Complexity

The power of binary search comes from how fast it cuts the search space. Each step halves the number of items left to check. After one step, about half remain. After two steps, about one quarter remain. After k steps, about 1/(2^k) remain. This means the number of steps grows very slowly as the list size grows. In computer science, we write this as O(log n) time complexity. The best case happens when the very first middle check hits the target, which is just one step. The worst and average case both still run in logarithmic time. Space usage is small too. The iterative version only keeps a few variables, so it uses O(1) extra space. The recursive version uses the call stack for each level of recursion, which can be O(log n) space in the worst case. The table below summarizes these facts so you can memorize them easily and explain them clearly in interviews or in class.

Case Time Complexity Space Complexity Notes
Best O(1) O(1) iterative, O(log n) recursive Target found at the first middle check
Average O(log n) O(1) iterative, O(log n) recursive Halves the search range each step
Worst O(log n) O(1) iterative, O(log n) recursive Range shrinks to empty without finding target

Common Mistakes and How to Avoid Them

Many bugs in binary search come from small index mistakes. One classic problem is an off-by-one error, which means using the wrong boundary or updating it wrong by only one. Always think carefully about whether the loop should continue while left < right or while left <= right. Using left <= right is common when you return as soon as you find the target. Another frequent bug is computing mid as (left + right) / 2, which can overflow when indexes are very large. Use left + (right – left) / 2 instead. Also, do not forget to move the boundary by one when you go to the other half: if you pick the right half, set left = mid + 1, and if you pick the left half, set right = mid – 1. Missing that +1 or -1 can lead to an infinite loop. Finally, remember that the array must be sorted in the correct order for the comparison logic to make sense. If the order is descending, flip the comparisons. If you are searching for a range or the first/last occurrence of a value with duplicates, adjust the rules slightly as shown in the variations below.

Variations: First/Last Occurrence and Bounds

Sometimes we do not just want to know if a value exists. We may want the first occurrence (leftmost index) or the last occurrence (rightmost index) when the array has duplicates. Or we may want the lower bound (the first index where the array value is greater than or equal to a target) or the upper bound (the first index where the array value is strictly greater than a target). These are easy changes to standard binary search. For example, to get the first occurrence, when we find the target at mid, we do not stop right away; instead we move to the left side to see if there is another same value earlier, and we keep track of the best index seen so far. For the last occurrence, we do the same but move to the right side on a match. For lower bound, we move left whenever arr[mid] is greater than or equal to the target, and for upper bound, we move left whenever arr[mid] is strictly greater than the target. This careful control of boundaries lets us answer many real tasks like finding insert positions, counting how many times a value appears, or slicing ranges fast.

First Occurrence (Leftmost)


public class FirstOccurrence {
    // Returns leftmost index of target if found, else -1
    public static int firstOccurrence(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        int ans = -1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (arr[mid] == target) {
                ans = mid;         // record and keep searching left
                right = mid - 1;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
}
    
  1. Keep a variable ans to remember the best index found so far.
  2. On a match, update ans and move right = mid – 1 to find an earlier match.
  3. If arr[mid] < target, move left = mid + 1.
  4. If arr[mid] > target, move right = mid – 1.
  5. Return ans at the end; it will be -1 if not found.

Lower Bound and Upper Bound


public class Bounds {
    // First index i where arr[i] >= x; returns arr.length if none
    public static int lowerBound(int[] arr, int x) {
        int left = 0, right = arr.length; // right is exclusive
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] >= x) {
                right = mid; // move left
            } else {
                left = mid + 1; // move right
            }
        }
        return left;
    }

    // First index i where arr[i] > x; returns arr.length if none
    public static int upperBound(int[] arr, int x) {
        int left = 0, right = arr.length; // right is exclusive
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] > x) {
                right = mid; // move left
            } else {
                left = mid + 1; // move right
            }
        }
        return left;
    }
}
    
  1. Use a half-open range [left, right) to simplify bounds logic.
  2. For lowerBound, if arr[mid] >= x, shrink right to mid; else grow left to mid + 1.
  3. For upperBound, if arr[mid] > x, shrink right to mid; else grow left to mid + 1.
  4. When the loop ends, left is the answer index.
  5. To count copies of x, compute upperBound(x) – lowerBound(x).

Advantages, Disadvantages, and Applications

Using binary search brings big speed gains when the data is sorted and random access is possible. But like every tool, it has limits. You should know both sides so you can choose wisely. The advantages are mainly about speed and simplicity. The disadvantages are about the need for sorted order and careful index handling. The applications are many, from everyday coding tasks to interview problems and real systems. When you see any situation where you can ask yes/no questions that cut the search range in half, think of binary search. Also, when you need to find the first value meeting some rule, or the last value before a rule breaks, a binary search on the answer often works well. You can even do binary search on the answer space, which means you are not searching in an array, but over a range of possible answers, checking feasibility with a helper function. Below are detailed lists you can use as a quick guide when planning your solution.

Advantages

  • Very fast: runs in O(log n) time, even for huge arrays.
  • Simple to implement and understand with practice.
  • Works for many variations like first/last occurrence and bounds.
  • Low memory use in the iterative version (O(1) extra space).
  • Great for repeated searches after one-time sorting.

Disadvantages

  • Needs sorted data; sorting first costs time.
  • Easy to make off-by-one errors in boundaries.
  • Does not work well on structures without random access (like simple linked lists).
  • Care required for integer overflow when computing mid.
  • In dynamic data with frequent inserts/deletes, keeping sorted order can be costly.

Applications

  • Lookups in sorted arrays or files (IDs, timestamps, prices, scores).
  • Finding insert positions using lowerBound/upperBound.
  • Counting duplicates: upperBound(x) – lowerBound(x).
  • Searching in dictionaries, phone books, or sorted logs.
  • Binary search on answer space: scheduling, capacity planning, minimizing the maximum value, or finding smallest feasible value that satisfies a condition.
  • Range queries when combined with prefix sums or ordered structures.

Practice Problems and Tips

To become strong at binary search, practice with many small tasks. Start with the basic search for a single value in a sorted array. Then try arrays with duplicates and find the first and last occurrence. Move on to tasks like finding lower bound and upper bound, and counting how many times a value appears. Next, try problems where you search for the first index that satisfies a condition, like “the first day when a number becomes bigger than X.” Later, test yourself with “binary search on answer” problems. Always write clear comments for left, right, and mid. Use the safe mid formula. Check the loop condition carefully. Make small tests where the array size is tiny, so you can trace every step by hand. Print the values of left, right, and mid during early testing to see the flow. If your loop gets stuck, check the boundary updates (+1 and -1). Write both iterative and recursive versions to understand the flow two ways. With steady practice, the pattern will feel natural and you will write correct code quickly.

  • Basic: search a target in a sorted array (no duplicates).
  • Moderate: search with duplicates, return first and last positions.
  • Moderate: implement lowerBound and upperBound; use them to count occurrences.
  • Advanced: given a condition function, find the first index where the condition is true.
  • Advanced: binary search on answer (e.g., minimize maximum load within a limit).

Summary

Binary search is a smart and fast way to find things in a sorted list. It works like a guessing game or like flipping through a dictionary: check the middle, then keep only the half that can contain the answer. This simple rule gives you O(log n) time, which is much faster than checking items one by one. To use it well, remember three keys: the data must be sorted; compute mid safely as left + (right – left) / 2; and update left and right correctly so you do not loop forever. Learn useful variations like finding the first/last occurrence and lower/upper bound. Watch out for off-by-one mistakes and overflow. Practice with small examples and print your variables to see what happens at each step. With these habits, binary search will feel easy, and you can apply it to many tasks, including finding positions, counting values, and even searching over possible answers to choose the best one. Keep it simple, and let the structure of sorted data give you free speed.



🤖 Chat with AI about “Binary Search”


Scroll to Top