Sometimes you don't want the answer — you want all of them: every subset, every permutation, every way to fill a knapsack. Exhaustive recursion (a.k.a. backtracking) systematically explores every possibility by making a choice, recursing, then undoing the choice to try the next one.
At each step you face a choice. Each choice branches the search:
Following every branch to the leaves enumerates the entire space.
function explore(choicesSoFar, remaining):
if nothingLeftToDecide:
record(choicesSoFar)
return
for each option in optionsFor(remaining):
choose(option)
explore(updatedChoices, smallerRemaining)
unchoose(option) // backtrack
The "unchoose" step (backtrack) restores state so the next branch starts clean. With immutable copies you can skip explicit undo — just pass new slices down.
Exhaustive search is inherently expensive: there are 2ⁿ subsets and n! permutations, so these algorithms are exponential or factorial. That's unavoidable when the output itself is that large. Backtracking's value is pruning — abandoning a branch as soon as it can't lead to a valid answer, so you skip whole regions of the tree.
The problems here drill the classic shapes: subsets, permutations, combinations, and choice-expansion. Learn to see the decision tree and the code writes itself.