Concepts / Base Cases in Recursive Algorithms

Base Cases in Recursive Algorithms

A recursive binary search has two base cases: an empty range, which returns -1, and a value found at the middle position, which returns that index.

  • Programming
Interactive lab

Try it: The Call Stack

How the call stack keeps track of function calls: each call pushes a frame with its own arguments and local variables, and each return pops it.

How it works

  1. Calling a function pushes a new frame on top of the stack; the caller pauses.
  2. The frame holds that call's parameters and local variables.
  3. When the function returns, its frame is popped and the return value goes back to the paused caller.
  4. A recursive function pushes one frame per call until the base case, then the frames unwind in reverse order.

Default run (17 steps): Program starts: main is about to call factorial(4). … The stack is empty again. Result: 24. Deepest point: 4 frames, 4 calls in total.

Educational simulation

Loading the simulation…

Predicting a Search That Comes Up Empty

Imagine a sorted array A holding the seven values 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6. A recursive binary search is asked to find the value 10 somewhere in this whole array. Before reading on, decide what the search should return. Think about whether 10 could ever be found, and what the algorithm should do once every remaining candidate has been ruled out.

What do you think happens?

What should the recursive binary search return when it is asked to find 10 in the array 3, 7, 11, 15, 19, 24, 30?

  • The index where 10 would be inserted, which is 2

  • -1, because 10 is not in the array

  • The index of the middle element, which is 3

  • The search never ends

Reveal answer
Answer:

-1, because 10 is not in the array

The search keeps narrowing the range until it becomes empty. An empty range is a base case that returns -1 immediately. The algorithm does not guarantee that it will find the value; it stops when either the value is located or there is nothing left to examine.

Many learners first picture recursive binary search as a simple chain of halvings that always ends by landing on the target. In that picture, the middle element is checked, the half containing the target is chosen, and the process repeats until the target turns up. The hidden assumption is that the search never faces a situation where nothing is left to examine. That assumption is wrong. It is one of the most frequent problems students hit when they write this routine themselves: they forget to handle the case where the portion of the list under consideration has no elements at all.

The correction changes how you should think about the recursion. A recursive search is not guaranteed to find the value. It keeps going until one of two things happens: the value is located, or the portion still being searched is empty. Both outcomes must be written into the routine as explicit cases. The rest of this article explains why each one is needed, and then traces the search for both a present value and an absent value.

Why the Range Needs Two Parameters

The original, non-recursive binary search could assume it was looking at an entire array. A recursive version cannot make that assumption, because each call must search only the portion of the array that remains under consideration. For that reason the recursive subroutine takes two extra parameters, loIndex and hiIndex. They mark the first and last positions of the range still being searched, inclusive at both ends. The value being sought is passed along unchanged.

This is a general lesson about recursion. To solve a problem recursively, the problem often has to be generalized a little. The original question was 'find this value in this array.' The generalized question is 'find this value between these two positions of this array.' The whole array is simply the special case where the range runs from 0 to the array length minus one, which is why the whole-array search is started with 0 and A.length minus 1.

The figure shows the state of the search after one comparison when looking for 24. The value 15 at the middle position is smaller than 24, so the portion at or before the middle is discarded, and the recursive call receives the range from index 4 through index 6. Nothing about the array has changed. Only the pair of parameters has moved, and that pair is all the recursive call needs to know about the part of the array it must examine.

Every recursive case works on a strictly smaller range than the one that produced it. The value is either smaller than the middle element, so the lower half is chosen, or larger, so the upper half is chosen. This shrinking is what guarantees that the recursion eventually reaches a base case.

Two Base Cases and What They Return

Definition

A base case is a case that is handled directly, without applying the algorithm recursively again. A recursive binary search has exactly two of them: an empty range, which returns -1, and a value found at the middle position, which returns that index.

The first base case is the empty range. If the starting index has moved past the ending index, there are no elements left to consider, so the value cannot be present. The answer -1 can be returned immediately. The empty-range check must come before any computation that depends on the range having elements, because an empty range has no middle element to examine.

The second base case is the successful middle check. If the value equals the element at the middle position, the index of that element is the answer, and no further recursion is needed. The search returns that index directly.

Every other situation is a recursive case. When the middle element is not the one sought, the search is handed to one of the two halves of the current range. The base cases are what stop the recursion. The recursive cases are what keep it moving toward them.

Key point

A recursive search is not guaranteed to find the value. It stops when the value is located at the middle, or when the range it is searching is empty. Both outcomes must be written as explicit cases in the routine.

Tracing a Search for a Present Value

Use the same array, 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6, and search for 24 in the whole array. The first call receives the range 0 through 6. The middle position of that range is index 3, which holds 15. Because 24 is larger than 15, the lower portion is discarded and the call makes a recursive call on the range 4 through 6.

Worked example

Searching for 24 in the Sorted Array

Find the value 24 in the array 3, 7, 11, 15, 19, 24, 30 using a recursive binary search that starts with loIndex 0 and hiIndex 6.

  1. 1
    First call: range 0 through 6

    The middle position of indices 0 through 6 is index 3, which holds 15. The value 24 is not equal to 15, so this is a recursive case, not a base case.

  2. 2
    Decide which half to search

    24 is larger than 15, so the lower half including the middle is discarded. The recursive call receives loIndex 4 and hiIndex 6. The array itself does not change.

  3. 3
    Second call: range 4 through 6

    The middle position of indices 4 through 6 is index 5, which holds 24. The value equals the middle element, so the successful middle check base case applies.

  4. 4
    Return the index upward

    The second call returns 5 immediately. The first call receives that value from its recursive call and passes the same 5 back to the original caller, since it did no further work of its own.

Result

The search returns 5, the index of 24 in the array.

Tracing a Search for an Absent Value

Now search for 10 in the same array. The value 10 is not present, so the search can only end when the range runs out of elements. Each call still follows the same rule: compare the value with the middle element, then discard the half that cannot contain it.

Trace the calls step by step. The first call receives range 0 through 6, with middle index 3 holding 15. Since 10 is smaller than 15, the upper portion is discarded and the second call receives range 0 through 2. Its middle index is 1, which holds 7. Since 10 is larger than 7, the third call receives range 2 through 2. Its middle index is 2, which holds 11. Since 10 is smaller than 11, the next range would be from index 2 through index 1. That range is empty, because the starting index has moved past the ending index. The empty-range base case applies, and the call returns -1 immediately.

The -1 does not stay inside the innermost call. Each earlier call received its answer from a recursive call and passes that same value back up to its own caller. The fourth call returns -1 to the third, the third returns -1 to the second, the second returns -1 to the first, and the first returns -1 to the original caller. The original caller therefore learns that 10 is not in the array.

Mistakes That Lead to Missing Base Cases

  • Common mistake

    Writing only the found-at-middle case and assuming the value will always be located

    What it looks like

    A routine that returns the middle index when the values match, and otherwise recurses left or right, with no check for an empty range.

    Why it fails

    The value may be absent. When the value is missing, the range eventually becomes empty, and the routine has no defined answer for that situation.

    Do this instead

    Add an explicit empty-range base case that returns -1 when the starting index has moved past the ending index.

  • Common mistake

    Computing the middle element before checking whether the range is empty

    What it looks like

    Finding the middle of range 2 through 1 and then comparing the value with an element at that position.

    Why it fails

    An empty range has no middle element, so the computation depends on elements that do not exist.

    Do this instead

    Place the empty-range check before any computation that uses the middle position.

  • Common mistake

    Forgetting that each recursive call must work on a smaller range

    What it looks like

    Recursing on the same range again after a comparison, without discarding the half that cannot contain the value.

    Why it fails

    If the range does not shrink, the recursion never reaches a base case and does not terminate.

    Do this instead

    Discard the half that cannot contain the value, so each call receives a strictly smaller range.

  • Common mistake

    Treating loIndex and hiIndex as if they always describe the whole array

    What it looks like

    Using index 0 and the array length in every recursive call instead of the current loIndex and hiIndex.

    Why it fails

    Each call must search only the portion still under consideration, so the whole-array bounds are wrong for any call after the first.

    Do this instead

    Pass the updated loIndex or hiIndex to each recursive call so it searches only its own range.

Practice: Trace Your Own Search

EASY

Using the sorted array 3, 7, 11, 15, 19, 24, 30 at indices 0 through 6, trace a recursive binary search for the value 19. For each call, write down loIndex, hiIndex, the middle index, the value at the middle, and the decision made. State what the original caller receives at the end.

Hints
  • Start with loIndex 0 and hiIndex 6. The middle index of that range is 3.
  • Compare 19 with the value at the middle. If it is larger, the lower half including the middle is discarded.
  • Check whether the value at the middle equals 19 before deciding that another call is needed.
MEDIUM

Trace the search for 8 in the same array. Identify the base case that ends the search and state the value the original caller receives.

Hints
  • Track which portion of the array remains after each comparison.
  • Ask whether the range ever becomes empty before 8 is found.

Key Takeaways

  • A recursive binary search has two base cases: an empty range, which returns -1, and a value found at the middle position, which returns that index.
  • The empty-range check must come before any computation that depends on the range having elements, because an empty range has no middle element.
  • The parameters loIndex and hiIndex mark the portion of the array still under consideration, and each recursive call works on a strictly smaller range.
  • Each recursive call passes its returned value back up to its caller, so the base case answer reaches the original caller unchanged.