Overview

15 Designing solutions with recursion and backtracking

This chapter presents recursion as a core software design technique and shows how pairing it with dynamic backtracking expands its power for search and constraint problems. Recursion tackles a task by reducing it to smaller, similar subproblems until hitting a base case, then “unwinding” results to solve the original problem. The chapter demystifies how recursive calls correspond to a loop’s initialization, iteration, and termination, stresses the need to shrink state toward a base case to avoid infinite recursion, and cautions that misuse can cause significant performance issues.

To ground the concepts, the text first uses simple demonstrations (finding the maximum in a list and reversing a list) to make base cases and recursive steps concrete, then highlights a classic fit for recursion with the Towers of Hanoi. It extends the idea to recursively defined data structures by implementing binary search tree operations—recursive insertion and inorder traversal for sorted output. The chapter then develops quicksort: choose a pivot, partition the list into two regions around it, and recursively sort the sublists, with small-size base cases ensuring progress and the pivot landing in its final position after each partition.

The chapter closes with both a warning and an upgrade in technique. It shows how a direct recursive implementation of the Fibonacci sequence explodes exponentially due to repeated subcalls, underlining the importance of understanding call counts, base cases, and recursion limits. It then introduces dynamic backtracking to explore decision trees efficiently: the eight queens puzzle is solved column by column by placing non-attacking queens and backtracking when stuck, and Sudoku is solved cell by cell by trying allowable numbers (respecting row, column, and block constraints) and backtracking on failure. Together, these patterns illustrate when recursion is elegant and effective and how backtracking turns it into a practical solver for complex search problems.

How the recursive function largest() finds the largest value in a list. The boxed values are the ever-shortening list arguments of the calls. The return value of each recursive call enables the caller to return its value.
How the recursive function reverse() reverses the contents of a list. Each call removes the first value, recursively reverses the rest of the list, and then appends the removed first value to the end.
The starting and ending positions of four disks in the Towers of Hanoi puzzle. We moved the disks from source pin L to destination pin R.
A conceptual solution with four disks that uses pin M as the temporary pin.
The base case of moving a single disk from pin L to pin R. No recursion needed.
The sequence of moves to get two disks from pin L to pin R using pin M as the temporary pin.
The sequence of moves to get three disks from pin L to pin R. During the steps, the pins change source, destination, and temporary pin roles.
A binary tree, where each nonempty node has zero, one, or two child nodes that are roots of subtrees. Each subtree is itself a binary tree, so a binary tree is recursively defined. This binary tree is also a binary search tree (BST), where at each nonempty parent node, values in the left child subtree are less than or equal to the parent node’s value, and values in the right subtree are greater. Each subtree is itself a BST, and so a BST is recursively defined.
The values were inserted into this BST in the order 42, 30, 5, 35, 63, 75, 25, 51, 44, 19, and 55. To insert the new value 60 into the tree, private method _insert() is called recursively until it reaches a base case of an empty subtree. The new node replaces the empty subtree, and the value is thereby inserted into the tree at its proper place.
Recursively printing the values of a BST in sorted order. At each parent node, first recursively print the values in its left child subtree if one exists, next print the parent node’s value, and then recursively print the values in the parent node’s right child subtree if one exists. The small numbers next to each node indicate the order that the nodes are printed.
The quicksort algorithm in action as it sorts a list. The circled values are the chosen pivot elements that create the sublists on both sides after partitioning. The rectangles enclose the sublists to be quicksorted recursively. In each line, the shaded sublist is the one actively being sorted. The values in bold are already in their correct places in the final sorted list. Note that the chosen pivot elements are always already in their correct places after partitioning.
Choosing 52 (circled) to be the pivot value and then partitioning the list into two sublists. Variables i and j are list indexes. After partitioning, all values to the left of 52 are less or equal, and all values to the right of 52 are greater. The two boxed sublists on either side of the pivot are now ready to be recursively quicksorted.
The tree of recursive calls to compute f(6). There are already many repeated calls with the same arguments. To compute f(n) as n becomes greater, the tree will grow larger, and the computation will take exponentially longer.
A decision tree showing recursion to move from one stage to the next and backtracking to try other paths in earlier stages.

Summary

  • Recursion is a software design technique that solves a problem by repeatedly dividing it into a smaller and smaller but similar subproblems. We recursively solve each subproblem the same way.
  • The recursion stops at a base case where the subproblem is so small that it has an obvious and immediate solution. Then the original problem is solved as the recursion unwinds.
  • When designed properly, a recursive programming solution can be simple and elegant. But improper use can lead to serious performance problems.
  • The combination of recursion and dynamic backtracking is a potent design tool for programs that explore multiple solution paths.
  • Each step can have multiple solution paths that are explored using recursion. After trying a path, backtrack and try the next path.
  • When all the solution paths at a step have been tried, backtrack to the previous step, and continue trying the previous step’s paths.
  • When we’ve backtracked to the starting step, either we found all the solutions, or we discovered that the problem is unsolvable.

FAQ

How does recursion compare to a for loop when designing solutions?Both approach repeated work with a start, progress, and stop condition. In a loop, you initialize a counter, update it each iteration, and stop when a limit is reached. In recursion, you start with the original problem, reduce it to a smaller but similar subproblem on each call, and stop at a base case where the solution is immediate. The key parallels are: initial state (original problem vs. counter init), progress (make the problem smaller vs. increment counter), and termination (base case vs. loop limit).
What is a base case in recursion and why is it critical?A base case is the smallest version of the problem whose solution is immediate (no further recursive calls). It prevents infinite recursion, lets the call stack start “unwinding,” and provides concrete results that combine to solve larger subproblems up to the original call.
How does the recursive largest() example find the maximum in a list?It compares the first element to the largest element of the rest of the list. Each call removes the first element and recurses on the smaller list until the base case (a single-element list), then returns back up, taking the larger of the two at each step. It’s a clear demo of recursion, though loops are more efficient here.
How does the recursive reverse() example reverse a list?Each call removes the first element, recursively reverses the remainder, then appends the removed element to the end. The base case is a single-element list. As the recursion unwinds, elements reattach in reverse order. Again, it’s instructional, but iteration or slicing is more efficient in practice.
How does the Towers of Hanoi algorithm use recursion?To move N disks from source to destination using a temporary peg: 1) move N−1 disks from source to temporary, 2) move the largest disk (base case when N=1) from source to destination, 3) move N−1 disks from temporary to destination. Each step reduces the problem size until the single-disk move base case.
Why are binary search trees (BSTs) a natural fit for recursive algorithms?BSTs are recursively defined: each node’s left and right children are roots of subtrees that are themselves BSTs. That makes operations like insert (recurse left/right until an empty link, then attach) and inorder traversal (left, node, right) simple and elegant with recursion.
How does Quicksort use recursion, and what are its base cases?Quicksort chooses a pivot, partitions the list so <= pivot values are left and > pivot values are right (placing the pivot in its final position), then recursively sorts the two sublists. Base cases: size 0–1 (already sorted) and size 2 (swap if needed). Partitioning is the critical step that enables the divide-and-conquer recursion.
What is the partition procedure in Quicksort with the i and j pointers?Typical steps: 1) pick a pivot (often near the middle), 2) park it at the right end, 3) move i right while values <= pivot, 4) move j left while values > pivot, 5) if i < j, swap those elements, 6) repeat until j crosses i, then swap the pivot into position i. The list is then split around the pivot for recursive sorting.
Why is the naive recursive Fibonacci implementation a performance disaster?It recomputes the same subproblems many times, creating an exponential number of calls and rapidly becoming slow (and risking hitting recursion limits). Prefer an iterative solution, memoization, or dynamic programming to reuse results and achieve linear time.
What is dynamic backtracking, and how do the eight queens and Sudoku solvers use it?Dynamic backtracking tries a choice at a step, recurses to the next step, and if it hits a dead end (no valid continuation), it backtracks and tries the next choice. Eight queens places one queen per column, checking row/diagonals for safety and backtracking when needed. Sudoku fills cells left-to-right, top-to-bottom, trying digits 1–9 that satisfy row/column/block constraints; if a later cell can’t be filled, it backtracks to try the next digit.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Software Design for Python Programmers ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Software Design for Python Programmers ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Software Design for Python Programmers ebook for free