Backtracking
Try a choice, explore, undo it. Systematic search over all possibilities.
Solving a maze, you walk down a corridor, hit a dead end, walk back to the last junction, and try the next corridor. You never restart from the entrance. You just undo your most recent choice. That is backtracking: a disciplined way to explore every possibility by trying a choice, exploring its consequences, and undoing it to try the next.
Interviewers love backtracking because one small template solves a whole family of famous problems: subsets, permutations, combinations, N-Queens, Sudoku, word search. Learn the template once and you can recognize its shape everywhere.
Problems as decision trees
Backtracking problems all share one structure: you build a solution one decision at a time, and each decision has a few options. Picture it as a tree. The root is the empty solution. Each level asks one question. Include this element or not? place the queen in which column?, and each branch is one answer. The leaves of the tree are complete candidate solutions.
Brute force would mean generating every leaf independently, redoing all the shared early decisions each time. Backtracking instead walks the tree with recursion, sharing the common prefix of decisions between sibling branches. It goes deep along one branch, and when that branch is finished (or provably hopeless), it steps back up exactly one level and takes the next branch.
The stepping back is the part that matters. To try the next branch honestly, the state of the world must look exactly like it did before you took the previous branch. That means every change you made on the way down must be reversed on the way back up.
The template: choose, explore, unchoose
Nearly every backtracking solution is three lines wrapped in a loop. Choose: make one of the available decisions, updating your in-progress solution. Explore: recurse to handle all the decisions that come after it. Unchoose: undo the decision so the loop can try the next option from a clean slate.
The in-progress solution is usually a shared, mutable object, a list you add to and remove from, or a board you mark and unmark. Sharing one object instead of copying it at every level is what keeps backtracking fast, and it is exactly why the unchoose step is mandatory. Forget to undo, and the second branch starts polluted with leftovers from the first; this is the single most common backtracking bug.
Here is the skeleton in Java. The base case fires when the solution is complete; the loop iterates over the choices legal at this point.
Worked example: all subsets of an array
Generate every subset of [1, 2, 3]. There are 2^n subsets because each element poses one yes-or-no decision: in or out. The decision tree has n levels, one per element.
We walk the array with an index. At each index we record the current partial subset as a valid answer, then loop over the remaining elements: choose one (add it to the list), explore everything that can follow it (recurse with the next index), and unchoose it (remove it from the list). The remove call is the backtrack.
Trace it briefly: start with []. Choose 1 giving [1], recurse; choose 2 giving [1,2], recurse; choose 3 giving [1,2,3]. No elements remain, so we return and remove 3, return and remove 2, giving [1] again. Now choose 3 giving [1,3]. Eventually 1 is removed too and the [2 ...] branch begins fresh. Every add is mirrored by exactly one remove, and all 8 subsets appear exactly once. Note the copy when recording a result: the shared list keeps mutating afterward, so we must snapshot it.
Pruning: skip branches that cannot win
Raw backtracking still visits an exponential tree, so the practical speedups come from pruning: detecting as early as possible that a branch cannot lead to a valid solution, and refusing to enter it. In the maze analogy, pruning is glancing down a corridor, seeing a wall right there, and not bothering to walk in.
Examples: in N-Queens, do not recurse into a square already attacked by a placed queen. In a combination-sum problem where all numbers are positive, stop the moment the running sum exceeds the target. Adding more can only make it worse. Sorting the input first often enables stronger pruning, because once one candidate is too big, all later candidates are too.
Pruning never changes correctness as long as you only cut branches that are provably hopeless; it just avoids wasted exploration. In interviews, stating your pruning rule out loud is an easy way to show depth. Backtracking explores all possibilities, which is sometimes unavoidable, but when subproblems start repeating across branches, there is a better tool, and that is exactly where dynamic programming picks up the story.
Frequently asked
What is the difference between backtracking and plain recursion?
Backtracking is recursion plus reversible state. Plain recursion just breaks a problem into smaller pieces, while backtracking builds a candidate solution step by step, mutating shared state on the way down and undoing each mutation on the way back so sibling branches start clean.
Why do I need to copy the current list before adding it to the results?
Because every level of the recursion shares the same list object, and it keeps changing after you record it. If you add the list itself, all your saved answers end up pointing at one object that finishes empty. Copying with new ArrayList<>(current) freezes a snapshot of that moment.
How slow is backtracking?
In the worst case it is exponential. Around 2^n for subsets and n! for permutations, because that is genuinely how many answers exist. That is acceptable when the problem demands enumerating all solutions and n is small, usually 20 or less. Pruning cuts the practical running time, but it cannot change the size of the full answer set.