Worked examples — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays
Before we start, one reminder of the three moving parts (all defined in the parent):
lo— the left edge of the window we still trust to maybe hold the target.hi— the right edge of that window.mid— the element half-way between them, computed asmid = lo + (hi - lo) // 2.n— the length (number of elements) of the array. Indices run from0ton - 1, so the very first window islo = 0,hi = n - 1.
The window [lo, hi] is the promise "if the target exists, it is somewhere in here." Every step shrinks it.
Both blocks below run this exact loop — keep it in view; every worked step is just one turn of it:
def bsearch(arr, target):
lo, hi = 0, len(arr) - 1 # n = len(arr); window is [0, n-1]
while lo <= hi: # guard: window still has >= 1 element
mid = lo + (hi - lo) // 2 # overflow-safe midpoint (see note above)
if arr[mid] == target:
return mid # hit
elif arr[mid] < target:
lo = mid + 1 # discard mid and everything left of it
else:
hi = mid - 1 # discard mid and everything right of it
return -1 # window emptied -> not presentAnd the rotated variant (used in Ex 6–9), which first names the sorted half:
def search_rotated(arr, target):
lo, hi = 0, len(arr) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if arr[mid] == target:
return mid
if arr[lo] <= arr[mid]: # LEFT half is sorted
if arr[lo] <= target < arr[mid]: # target lies inside it
hi = mid - 1
else:
lo = mid + 1
else: # RIGHT half is sorted
if arr[mid] < target <= arr[hi]: # target lies inside it
lo = mid + 1
else:
hi = mid - 1
return -1The scenario matrix
Every binary-search problem you will ever be handed falls into exactly one of these cells. The job of this page is to leave no cell un-worked.
| # | Cell (what makes it special) | Array kind | Worked in |
|---|---|---|---|
| C1 | Target sits in the middle — found on step 1 | plain sorted | Ex 1 |
| C2 | Target at the first index (left boundary) | plain sorted | Ex 2 |
| C3 | Target at the last index (right boundary) | plain sorted | Ex 2 |
| C4 | Target absent, between two values → -1 |
plain sorted | Ex 3 |
| C5 | Target smaller than everything / larger than everything (out of range) | plain sorted | Ex 3 |
| C6 | Degenerate: empty array, single element | plain sorted | Ex 4 |
| C7 | Duplicates — which index do we get? | plain sorted | Ex 5 |
| C8 | Rotated, target in the un-sorted half | rotated | Ex 6 |
| C9 | Rotated, target in the sorted half | rotated | Ex 6 |
| C10 | Rotated, zero rotation (array not actually turned) | rotated | Ex 7 |
| C11 | Real-world word problem (mapping a story to indices) | plain sorted | Ex 8 |
| C12 | Exam twist — count steps / find the pivot | rotated | Ex 9 |
We will hit all twelve in nine examples.
The figure below is our map of the scenario matrix. It draws the workhorse array as ten cyan-outlined boxes on the blueprint grid, each labelled with its value on top and its index [0]..[9] underneath. Three amber arrows point down onto boxes — these are the cells where the search lands on a real element: the left one labelled "first index (C2)" hits box [0] (value 2), the centre one "middle hit (C1)" hits box [4] (value 16), the right one "last index (C3)" hits box [9] (value 91). Three cyan arrows point at empty space — these are the misses: one rises from the seam between boxes [4] and [5] labelled "gap = miss (C4) 20?" (there is no box there, so 20 falls through), one sits left of box [0] labelled "< all (C5) 1?", and one sits right of box [9] labelled "> all (C5) 100?". The lesson the picture teaches at a glance: amber = a box to land on, cyan = no box exists, which is exactly why those searches run the window off an end.

Plain sorted array examples
We use one workhorse array for the first block:
Here , indices 0..9. Since , the worst case is comparisons.
Rotated array examples
Recall from the parent: a rotated sorted array is a sorted array cut at one point and its front moved to the back, e.g. [4,5,6,7,0,1,2]. It is not globally sorted, but at least one side of mid is always sorted — the fact we derived above.
The figure below shows exactly how the rotated test reads. It draws rot = [15,18,2,3,6,8,11] as seven boxes with indices [0]..[6] underneath. The two amber-outlined boxes on the left (15, 18) are the "wrapped-high" front — the piece that used to be at the end and got moved forward. The five cyan-outlined boxes (2,3,6,8,11) are the sorted tail. A white arrow drops onto box [3] labelled "mid = 3, arr[mid]=3". Underneath the boxes are two coloured spans: an amber line under [0..3] labelled "left half: NOT sorted (pivot 18→2 inside)" — it crosses the cliff, so it is not sorted — and a cyan line under [3..6] labelled "right half: SORTED 3 < 6 < 8 < 11". Across the top, a white caption states the exact decision the code makes: "arr[lo]=15 > arr[mid]=3 ⇒ right side is the clean one." Read the picture as: whenever the left edge value exceeds the mid value, the cliff hides on the left, so trust the right span.

We use (indices 0..6, original sorted order rotated so that 15, 18 moved to the front).
find 6 then find 18
Forecast: for 6, the sorted half will be the right side; for 18, it will be the left side. Watch which branch fires.
find 6 (target ends up in a sorted right half — C8/C9):
lo=0, hi=6, mid=3.arr[3]=3. Not equal. Isarr[lo]=15 <= arr[mid]=3? No → the right half[3..6]is the sorted one. Why? If the left edge is bigger thanmid, the cliff (the wrap-around drop) must be inside the left half, so the right half is clean and sorted.- Right half sorted → check
arr[mid]=3 < 6 <= arr[hi]=11? Yes → target is in the right →lo = mid+1 = 4. Why? In a sorted half we can range-check with plain</<=: since 6 lies betweenarr[mid]andarr[hi], it must be to the right ofmid, so discardmidand everything left. lo=4, hi=6, mid=5.arr[5]=8. Not equal.arr[lo]=6 <= arr[mid]=8? Yes → left half[4..5]sorted. Why? Now the left edge is not bigger thanmid, so no cliff sits in the left span; that span is sorted.- Is
arr[lo]=6 <= 6 < arr[mid]=8? Yes → target in left →hi = mid-1 = 4. Why? 6 lies in the sorted left range[6, 8), so it is at or left ofmid; discardmidand everything right. lo=4, hi=4, mid=4.arr[4]=6→ return 4. Why? Window collapsed to the single cell[4,4];lo <= hilets us test it and it matches.
find 18 (target ends up in a sorted left half — C9):
lo=0, hi=6, mid=3,arr[3]=3. Not equal. Isarr[lo]=15 <= 3? No → right half[3..6]sorted. Why? Same detector as before: left edge bigger thanmidmeans the cliff is on the left, so the right half is clean.- Is
arr[mid]=3 < 18 <= arr[hi]=11? No → target NOT in the sorted right →hi = mid-1 = 2. Why? 18 falls outside the sorted right range(3, 11], so it cannot be there; the only place left is the (unsorted) left half, so shrinkhi. lo=0, hi=2, mid=1,arr[1]=18→ return 1. Why? Newmidlands directly on 18; equality returns immediately.
Verify: rot[4]=6, rot[1]=18. ✓ In every step we first named the sorted half, then used plain range logic there.
find 8 in [2,3,6,8,11,15,18]
Forecast: "rotated by 0" means it is still fully sorted — there is no cliff at all. Does the rotated algorithm still work, or does it need a special case?
lo=0, hi=6, mid=3.arr[3]=8 == 8→ return 3. Why this step?mid=3happens to land on the target directly, so equality returns at once — no half-picking needed this time.
Now trace find 15 in the same array to exercise the branch logic when there's no lucky centre hit:
lo=0, hi=6, mid=3.arr[3]=8. Not equal. Isarr[lo]=2 <= arr[mid]=8? Yes → left half[0..3]sorted. Why? With zero rotation the whole array is sorted, soarr[lo] <= arr[mid]is always true — the "left half sorted" branch fires every single time, exactly like ordinary binary search.- Is
arr[lo]=2 <= 15 < arr[mid]=8? No (15 is not below 8) → target not in the sorted left →lo = mid+1 = 4. Why? 15 lies outside the left range[2, 8), so discard the left half by raisinglo. lo=4, hi=6, mid=5.arr[5]=15→ return 5. Why? Newmidlands on 15; equality returns.
Verify: arr[3]=8 and arr[5]=15. ✓ Key lesson: the rotated algorithm is a superset — with zero rotation the arr[lo] <= arr[mid] test is always true, so it degrades gracefully into plain binary search with no extra code.
Story: A library shelf holds books sorted by their 13-digit ISBN. The shelf currently has ISBN codes (last 3 digits shown) [104, 219, 337, 402, 588, 613, 771, 890] at slots 0..7. A robot must fetch the book with code 588. How many shelf-slots does it physically open?
Forecast: 8 books, so worst case opens. Will 588 need all three?
- Map the story to indices. "sorted by ISBN" = sorted array; "fetch code 588" =
target=588. Heren = 8, solo=0, hi=7. Why? Binary search only applies once we confirm the data is sorted — the problem states it is. mid = 0+(7-0)//2 = 3(since7 // 2 = 3).arr[3]=402 < 588→lo=4. (open #1) Why? 402 is below 588, so the book (if shelved) is to the right of slot 3; discard slots 0–3 by raisinglo.lo=4, hi=7, mid=5.arr[5]=613 > 588→hi=4. (open #2) Why? 613 overshoots 588, so the book is to the left of slot 5; discard slots 5–7 by loweringhi.lo=4, hi=4, mid=4.arr[4]=588→ return 4. (open #3) Why? Window is the single slot[4,4];lo <= hilets the robot open it, and it is the book.
Verify: arr[4]=588, found in 3 opens. Units check: "opens" = comparisons, and . ✓ Compare with Linear Search: the robot would have opened 5 slots (0..4) scanning left-to-right.
pivot (smallest element) of a rotated array
Statement: In rot = [15, 18, 2, 3, 6, 8, 11], find the index of the minimum without scanning all elements. (This is the rotation point — the bottom of the cliff, where the original array wrapped.)
Forecast: the minimum is the one spot where a bigger number is immediately followed by a smaller one. Can we binary-search that? Yes — compare arr[mid] to arr[hi].
lo=0, hi=6, mid=3.arr[3]=3vsarr[hi]=arr[6]=11. Isarr[mid] > arr[hi]?3 > 11? No → the minimum is atmidor to its left →hi = mid = 3. Why this step? Ifarr[mid] <= arr[hi], the right side[mid..hi]is sorted (no cliff), so its smallest isarr[mid]itself; we never need to look right ofmid— but we keepmid(no-1) becausemidmight BE the minimum.lo=0, hi=3, mid=1.arr[1]=18vsarr[3]=3. Is18 > 3? Yes → minimum is strictly to the right →lo = mid+1 = 2. Why?arr[mid]=18is bigger than the right edgearr[hi]=3, so the cliff lies to the right ofmid;midcannot be the minimum, hence+1.lo=2, hi=3, mid=2.arr[2]=2vsarr[3]=3.2 > 3? No →hi = mid = 2. Why?arr[mid]=2 <= arr[hi]=3, so the right side is sorted and the minimum is atmidor left of it; keepmid, shrinkhito it.lo=2, hi=2→ window is one element → return 2. Why? Whenlo == hithe search space is a single index, which must be the minimum we cornered; thelo < hiloop stops here.
Verify: rot[2]=2, which is indeed the smallest value in rot; the array was rotated by 2 positions. ✓ Still — each step halves the window. This is the trick a Binary Search Tree does not need (its structure already encodes order), but here we recover the pivot with the same halving idea.
Case coverage checklist
Recall Did we really hit every cell?
Middle hit ::: Ex 1 (C1) First / last index ::: Ex 2 (C2, C3) Absent between two values ::: Ex 3 (C4) Out of range both sides ::: Ex 3 (C5) Empty and single-element ::: Ex 4 (C6) Duplicates ::: Ex 5 (C7) Rotated, unsorted half ::: Ex 6 (C8) Rotated, sorted half ::: Ex 6 & 7 (C9) Rotated by zero ::: Ex 7 (C10) Word problem ::: Ex 8 (C11) Exam twist (find pivot) ::: Ex 9 (C12)
"Centre, Edge, Miss, Empty, Dupe, Rotate." Six words = every test case you should throw at any binary search you write, in order.
See also
- Parent topic
- Why halving is : Logarithms, Recurrence Relations, Time Complexity & Big-O
- The slower baseline: Linear Search
- Boundary variants (first/last occurrence): Lower & Upper Bound (bisect)
- Order-as-structure instead of order-in-array: Binary Search Tree
- Getting data sorted first: Sorting Algorithms