0Pricing
DSA Interview Prep · Lesson

Classic Binary Search: Left, Right, Mid

Implement binary search iteratively and recursively, nail the off-by-one details for lo/hi boundaries, and verify correctness with edge-case inputs.

Classic Binary Search: Left, Right, Mid is a free DSA Interview Prep lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DSA Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Binary Search Matters

Binary search reduces an O(n) linear scan to O(log n) by halving the search space at every step. In an array of one million elements a linear scan needs up to 1,000,000 comparisons, but binary search needs at most 20. This efficiency makes it one of the most commonly tested algorithms in coding interviews.

The core insight is that a sorted array lets you decide, after a single comparison, which half of the remaining data to discard entirely.

The Left, Mid, Right Framework

Binary search uses three index pointers: lo (left boundary), hi (right boundary), and mid (midpoint). At each iteration you compute mid = (lo + hi) // 2 and compare the target against arr[mid]. If the target is smaller, move hi = mid - 1; if larger, move lo = mid + 1; if equal, you found it.

The loop continues while lo <= hi. When the loop exits without finding the target, return -1.

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

print(binary_search([1, 3, 5, 7, 9, 11], 7))  # 3
print(binary_search([1, 3, 5, 7, 9, 11], 6))  # -1

Avoiding Integer Overflow in Mid

The expression mid = (lo + hi) // 2 can cause integer overflow in languages with fixed-width integers (Java, C++). Python integers are arbitrary-precision so overflow never occurs, but interviewers still expect you to know the safe alternative: mid = lo + (hi - lo) // 2.

This form computes the same midpoint but adds only the half-distance to lo rather than summing both pointers first. Mentioning this in an interview signals awareness of low-level concerns.

# Safe mid calculation (important in Java/C++, good habit in Python too)
lo, hi = 0, 1_000_000_000
mid_unsafe = (lo + hi) // 2   # fine in Python
mid_safe   = lo + (hi - lo) // 2  # same result, no overflow risk
print(mid_unsafe == mid_safe)  # True

Inclusive vs Exclusive Boundaries

One of the trickiest parts of binary search is choosing whether hi points to the last valid index (inclusive, hi = len(arr) - 1) or one past the end (exclusive, hi = len(arr)). Different conventions require different loop conditions and boundary updates.

With inclusive boundaries use while lo <= hi and update hi = mid - 1. With exclusive boundaries use while lo < hi and update hi = mid. Mixing conventions is the single most common source of bugs in binary search implementations.

# Exclusive hi variant — useful for bisect-style lower-bound
def search_exclusive(arr, target):
    lo, hi = 0, len(arr)  # hi is one past last
    while lo < hi:          # strictly less than
        mid = lo + (hi - lo) // 2
        if arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid         # NOT mid - 1
    return lo if lo < len(arr) and arr[lo] == target else -1

print(search_exclusive([2, 4, 6, 8, 10], 6))  # 2

Recursive Binary Search

Binary search can be written recursively by passing updated lo and hi bounds through the call stack. Each recursive call reduces the search space by half, so the depth is O(log n). The base case is when lo > hi (not found) or arr[mid] == target (found).

The iterative version is preferred in production code because it avoids stack-frame overhead, but the recursive version communicates the divide-and-conquer structure more clearly on a whiteboard.

def binary_search_rec(arr, target, lo, hi):
    if lo > hi:
        return -1
    mid = lo + (hi - lo) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_rec(arr, target, mid + 1, hi)
    else:
        return binary_search_rec(arr, target, lo, mid - 1)

arr = [1, 3, 5, 7, 9, 11]
print(binary_search_rec(arr, 9, 0, len(arr) - 1))  # 4

Edge Cases: Empty Array, Single Element

Robust binary search must handle edge cases without crashing. The three most common are: an empty array (the loop never executes and -1 is returned correctly), a single-element array (mid equals lo equals hi, one comparison suffices), and targets outside the range (lo eventually exceeds hi and -1 is returned).

Always verify your implementation against these inputs before moving to follow-up questions in an interview.

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

print(binary_search([], 5))       # -1  (empty)
print(binary_search([7], 7))      # 0   (single, found)
print(binary_search([7], 3))      # -1  (single, not found)
print(binary_search([1,3,5], 0))  # -1  (below range)
print(binary_search([1,3,5], 9))  # -1  (above range)

Time and Space Complexity

Binary search has O(log n) time complexity because each comparison halves the search space. After k comparisons the remaining space is n/2^k; the search ends when this reaches 1, so k = log₂ n.

Space complexity is O(1) for the iterative version (only three integer variables) and O(log n) for the recursive version due to the call stack depth. In an interview always state both and prefer the iterative form when space is constrained.

import math

for n in [10, 100, 1000, 1_000_000, 1_000_000_000]:
    steps = math.ceil(math.log2(n + 1))
    print(f'n={n:>12,}  max comparisons={steps}')

Searching for Exact Match vs Boundary

Classic binary search returns any index where the target exists. But many interview problems ask for the first or last occurrence of a target. For those you must continue searching even after finding a match — instead of returning immediately, narrow the boundary and keep going.

When searching for the first occurrence, after finding arr[mid] == target, record mid as a candidate and set hi = mid - 1. For the last occurrence set lo = mid + 1.

def first_occurrence(arr, target):
    lo, hi, result = 0, len(arr) - 1, -1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if arr[mid] == target:
            result = mid
            hi = mid - 1   # keep searching left
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return result

print(first_occurrence([1, 2, 2, 2, 3], 2))  # 1

Using Python's bisect Module

Python's standard library provides bisect.bisect_left(arr, x) and bisect.bisect_right(arr, x) for production-ready binary search. bisect_left returns the leftmost index where x can be inserted to keep the array sorted, effectively finding the first position where arr[i] >= x.

Interviewers may allow you to use bisect; always confirm first. Knowing how it works under the hood (it is O(log n) binary search) is still essential.

import bisect

arr = [1, 2, 2, 2, 3, 5]

print(bisect.bisect_left(arr, 2))   # 1  (first 2)
print(bisect.bisect_right(arr, 2))  # 4  (after last 2)

# Check if target exists
target = 3
idx = bisect.bisect_left(arr, target)
print(idx < len(arr) and arr[idx] == target)  # True

Common Binary Search Pitfalls

Three mistakes cause most binary search bugs in interviews. First, wrong loop condition: using < instead of <= with inclusive boundaries causes the last remaining element to be skipped. Second, incorrect boundary update: forgetting the +1 or -1 creates an infinite loop when lo == hi. Third, operating on an unsorted array: binary search is only correct on sorted data.

Before writing any binary search, state aloud: 'The array is sorted, my boundaries are inclusive, and my loop runs while lo <= hi.'

# BUG: infinite loop when lo == hi because hi = mid never moves past lo
def buggy(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo < hi:              # should be lo <= hi for exact-match
        mid = lo + (hi - lo) // 2
        if arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid            # stops, but never returns mid when found
    return lo if arr[lo] == target else -1

print(buggy([1, 3, 5, 7], 7))  # 3 (works here by luck)
print(buggy([1, 3, 5, 7], 1))  # 0 (correct)
print(buggy([1, 3, 5, 7], 4))  # -1 (correct)

Interview Tips for Binary Search

When you see a problem on a sorted array, a monotonically increasing function, or a search space that can be halved, immediately consider binary search. In an interview, narrate your thinking: 'Since the array is sorted, I can discard half the elements per comparison, giving O(log n).'

Always verify your solution on at least three inputs: a value at the start, a value at the end, and a value that is absent. Stating complexity proactively — 'time O(log n), space O(1)' — before being asked signals strong fundamentals.

Quick Check

Test your understanding of Data Structures & Algorithms — Coding Interview Prep concepts from this lesson.

Lesson Recap

In this lesson you learned: binary search halves the search space each step for O(log n) time, the inclusive boundary convention uses lo <= hi with updates lo = mid+1 and hi = mid-1, and to find first/last occurrences you continue searching after a match rather than returning immediately. Next up we explore how binary search extends to rotated and unsorted arrays.

Frequently asked questions

Is the “Classic Binary Search: Left, Right, Mid” lesson free?

Yes — the full text of “Classic Binary Search: Left, Right, Mid” is free to read here on the web, and the DSA Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DSA Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Classic Binary Search: Left, Right, Mid”?

Implement binary search iteratively and recursively, nail the off-by-one details for lo/hi boundaries, and verify correctness with edge-case inputs. You practise DSA Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DSA Interview Prep?

No prior experience is required. DSA Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Classic Binary Search: Left, Right, Mid” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DSA Interview Prep lesson?

Yes. Every DSA Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Classic Binary Search: Left, Right, Mid
  2. Binary Search on Rotated and Unsorted Arrays
  3. Lower Bound and Upper Bound
  4. Answer-Space Binary Search
← Back to DSA Interview Prep