PrepGo

Recursive Searching and Sorting - AP Computer Science A Study Guide

Written by AP Content Team, Verified for 2026 AP Exams, Last updated: May 2026

Learn with study guides reviewed by top AP teachers. This guide takes about 12 minutes to read.

Getting Started

While loops provide a powerful way to repeat tasks, some problems are more naturally solved by breaking them into smaller, self-similar subproblems. This chapter introduces recursion, a programming technique where a method calls itself to solve a problem. We will explore how this "divide and conquer" strategy is used to implement two highly efficient algorithms: binary search and merge sort.

What You Should Be Able to Do

  • Trace the execution of a recursive method, tracking its parameters and return values.

  • Identify the base case and recursive step in a recursive algorithm.

  • Implement a recursive binary search on a sorted array.

  • Trace the splitting and merging steps of the merge sort algorithm.

  • Understand how a missing or incorrect base case leads to a StackOverflowError.

Key Concepts & Java Implementation

The Core Idea

Recursion is a problem-solving approach where the solution to a problem depends on solutions to smaller instances of the same problem. In Java, this is achieved when a method calls itself. Think of it like a set of Russian nesting dolls: to open the whole set, you open the largest doll, which contains a slightly smaller doll, and you repeat this process until you reach the smallest doll that cannot be opened.

Every well-formed recursive algorithm has two essential parts:

  1. Base Case: The simplest possible instance of the problem, which can be solved directly without further recursion. This is the condition that stops the recursive calls. In the nesting doll analogy, this is the smallest, solid doll.

  2. Recursive Step: The part of the algorithm that breaks the problem down into a smaller piece and calls the method on that smaller piece. This step must move the problem closer to the base case with each call.

This strategy is often called divide and conquer. The problem is divided into subproblems, the subproblems are solved (conquered) recursively, and then their solutions are combined if necessary.

Syntax & Implementation

Recursion doesn't introduce new Java keywords, but rather a new pattern of method invocation. The key is a method calling itself with modified arguments that progress toward the base case.

Annotated Java Example: Recursive Binary Search

Binary search is a fast algorithm for finding a target value within a sorted array. It works by repeatedly dividing the search interval in half.


public class Searcher {

    /**

     * Searches for a target value in a sorted array using recursion.

     * @param arr The sorted array to search in.

     * @param target The value to search for.

     * @param low The starting index of the current search interval.

     * @param high The ending index of the current search interval.

     * @return The index of the target, or -1 if not found.

     */

    public static int binarySearch(int[] arr, int target, int low, int high) {

        // Base Case: If the search interval is invalid, the target is not in the array.

        if (low > high) {

            return -1;

        }


        // Find the middle index of the current interval.

        int mid = low + (high - low) / 2;


        // Recursive Step: Compare the middle element with the target.

        if (arr[mid] == target) {

            // Base Case: The target is found at the middle index.

            return mid;

        } else if (arr[mid] > target) {

            // The target must be in the left half. Make a recursive call on the left subarray.

            return binarySearch(arr, target, low, mid - 1);

        } else {

            // The target must be in the right half. Make a recursive call on the right subarray.

            return binarySearch(arr, target, mid + 1, high);

        }

    }


    public static void main(String[] args) {

        int[] sortedData = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};

        // Initial call to start the search on the entire array (indices 0 to 9).

        int result = binarySearch(sortedData, 23, 0, sortedData.length - 1);

        System.out.println("Found at index: " + result); // Output: Found at index: 5

    }

}

Annotated Java Example: Merge Sort

Merge sort is an efficient, recursive sorting algorithm. It divides an array into two halves, recursively sorts them, and then merges the two sorted halves back together.


import java.util.Arrays;


public class Sorter {

    /**

     * The main method that initiates the merge sort process.

     * @param arr The array to be sorted.

     */

    public static void mergeSort(int[] arr) {

        // A helper method is often used to pass array indices.

        mergeSort(arr, 0, arr.length - 1);

    }


    /**

     * The recursive method that divides the array and calls the merge helper.

     * @param arr The array to be sorted.

     * @param left The starting index of the segment to sort.

     * @param right The ending index of the segment to sort.

     */

    private static void mergeSort(int[] arr, int left, int right) {

        // Base Case: If the segment has 0 or 1 elements, it's already sorted.

        if (left < right) {

            // Find the middle point to divide the array into two halves.

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


            // Recursive Step: Sort the first and second halves.

            mergeSort(arr, left, mid);

            mergeSort(arr, mid + 1, right);


            // Merge the sorted halves.

            merge(arr, left, mid, right);

        }

    }


    /**

     * Merges two sorted subarrays into one sorted array.

     * Subarrays are arr[left...mid] and arr[mid+1...right].

     */

    private static void merge(int[] arr, int left, int mid, int right) {

        // Create temporary arrays to hold the two halves.

        int[] leftArr = Arrays.copyOfRange(arr, left, mid + 1);

        int[] rightArr = Arrays.copyOfRange(arr, mid + 1, right + 1);


        int i = 0, j = 0; // Initial indices of first and second subarrays

        int k = left;     // Initial index of merged subarray in original array


        // Merge the temp arrays back into the original array arr[left...right]

        while (i < leftArr.length && j < rightArr.length) {

            if (leftArr[i] <= rightArr[j]) {

                arr[k] = leftArr[i];

                i++;

            } else {

                arr[k] = rightArr[j];

                j++;

            }

            k++;

        }


        // Copy remaining elements of leftArr[], if any

        while (i < leftArr.length) {

            arr[k] = leftArr[i];

            i++;

            k++;

        }


        // Copy remaining elements of rightArr[], if any

        while (j < rightArr.length) {

            arr[k] = rightArr[j];

            j++;

            k++;

        }

    }

}

Tracing & Analysis

Execution Trace: Binary Search

Let's trace binarySearch(sortedData, 23, 0, 9) where sortedData is {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}.

Call #lowhighmidarr[mid]Action
10941616 < 23, search right half. Call binarySearch(..., 5, 9)
25975656 > 23, search left half. Call binarySearch(..., 5, 6)
35652323 == 23, target found. Return mid (which is 5).

Analysis: Merge Sort

Merge sort's power comes from the merge step. Dividing the array is fast, but the real work happens when merging two already sorted lists. Because they are sorted, we can merge them in a single pass by simply comparing the front elements of each list and picking the smaller one. This process is highly efficient. While more complex to write than simpler sorts like selection sort, merge sort's performance on large datasets is significantly better.

Java Syntax Quick-Reference

This topic uses existing Java syntax in a new, recursive pattern.

  • methodName(newArgs): A method calling itself is the core of recursion. The arguments newArgs must be different from the original call to move closer to a base case.

  • if (baseCaseCondition): An if statement used to check for the base case and stop the recursion, typically by returning a value directly.

  • return methodName(newArgs): In a method that returns a value, the recursive call is often part of the return statement. This passes the result from the deepest call all the way back to the initial call.

Core Code Examples & Terminology

  • Recursion: The process in which a method calls itself one or more times in its body, either directly or indirectly, to solve a problem.

  • Base Case: A condition within a recursive method that stops the recursion. It represents the simplest version of the problem that can be solved without further recursive calls.

  • Recursive Step: The part of a recursive method that calls itself, but with modified parameters that move the problem closer to the base case.

  • Divide and Conquer: An algorithmic paradigm where a problem is broken down into smaller, similar subproblems, which are solved recursively, and their solutions are combined to solve the original problem.

  • Core Snippet 1 (Recursive Call Logic):

    
    if (arr[mid] > target) {
    
        // The recursive step moves closer to the base case by reducing the search range.
    
        return binarySearch(arr, target, low, mid - 1);
    
    }
    

    This code demonstrates a recursive step where the method calls itself with an updated high boundary.

  • Core Snippet 2 (Base Case Logic):

    
    if (low > high) {
    
        // The base case is met when the search range is empty.
    
        return -1; // Indicates the target was not found.
    
    }
    

    This code shows a base case in a binary search that stops recursion when the element cannot be found.

  • Core Snippet 3 (Merge Sort Division):

    
    if (left < right) {
    
        int mid = left + (right - left) / 2;
    
        mergeSort(arr, left, mid);      // Recurse on left half
    
        mergeSort(arr, mid + 1, right); // Recurse on right half
    
        merge(arr, left, mid, right);
    
    }
    

    This snippet shows the "divide" part of merge sort, where the array is split and the method recursively calls itself on both halves.

Core Skill Check

  • Code Tracing: What is the output of mystery(3)?

    
    public int mystery(int n) {
    
        if (n == 0) return 1;
    
        return n * mystery(n - 1);
    
    }
    

    Answer: 6 (It calculates 3 * 2 * 1 * 1)

  • Debugging: Identify the error in this recursive code that causes it to fail.

    
    public void countDown(int num) {
    
        System.out.println(num);
    
        countDown(num - 1); // The method never stops calling itself.
    
    }
    

    Answer: There is no base case. This will cause a StackOverflowError as the method calls itself infinitely.

  • Application: Write a single line of Java code for the base case of a recursive method sum(int n) that calculates the sum of integers from 1 to n.

    Answer: if (n <= 1) return n; or if (n == 1) return 1;

Common Misconceptions & Errors

  • Infinite Recursion: Forgetting a base case or writing a recursive step that doesn't move closer to the base case will cause a StackOverflowError. This error means the program has run out of memory for tracking method calls.

  • Forgetting to return a Result: In a recursive method that returns a value (like binary search), you must return the result of the recursive call (e.g., return binarySearch(...)). Forgetting the return keyword is a common mistake.

  • Incorrect Parameter Updates: In the recursive step, passing the wrong arguments can lead to incorrect results or infinite recursion. For example, in binary search, passing mid instead of mid - 1 or mid + 1 can cause the search to get stuck.

  • Off-by-One Errors: When calculating indices like mid, low, or high, it's easy to be off by one, which can exclude a necessary element from the search or cause an ArrayIndexOutOfBoundsException.

Summary

Recursion is a powerful technique for solving problems that can be broken down into smaller, self-similar versions. Every recursive method requires a base case to stop the recursion and a recursive step that moves the problem toward the base case. We explored two classic "divide and conquer" algorithms: recursive binary search, which efficiently finds elements in a sorted array, and merge sort, which provides a consistently fast way to sort data. Mastering recursion involves understanding how to trace the flow of method calls and ensuring that every path eventually leads to a terminating base case.