?) {
// Check whether the state is a solution
if (isSolution(state)) {
// Record the solution
recordSolution(state, res)
// Stop searching this branch
return
}
// Try every choice
for (choice in choices) {
// Prune: check whether the choice is valid
if (isValid(state, choice)) {
// Try: make the choice and update the state
makeChoice(state, choice)
backtrack(state, choices, res)
// Backtrack: undo the choice and restore the previous state
undoChoice(state, choice)
}
}
}
```
=== "Ruby"
```ruby title=""
### Backtracking template ###
def backtrack(state, choices, res)
# Check whether the state is a solution
if is_solution?(state)
# Record the solution
record_solution(state, res)
return
end
# Try every choice
for choice in choices
# Prune: check whether the choice is valid
if is_valid?(state, choice)
# Try: make the choice and update the state
make_choice(state, choice)
backtrack(state, choices, res)
# Backtrack: undo the choice and restore the previous state
undo_choice(state, choice)
end
end
end
```
Now apply this template to Example 3. The `state` is the current path of visited nodes, `choices` consists of the current node's left and right children, and `res` is the list of paths:
```src
[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack}
```
The problem requires us to keep searching after finding a node with value $7$, **so remove the `return` after recording a solution**. The figure below compares the searches with and without that `return`.

The template-based implementation is more verbose than the preorder version, but more general. **Many backtracking problems can be solved within this template**: define `state` and `choices` for the problem, then implement each helper method.
## Common terminology
The table below summarizes common backtracking terms and illustrates them with Example 3, helping us analyze problems more clearly.
Table Common backtracking terminology
| Term | Definition | Example 3 |
| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Solution | An answer satisfying the problem's conditions; there may be one or more | All paths from the root to a node valued $7$ that satisfy the constraint |
| Constraint | A condition restricting which solutions are feasible, often used for pruning | The path contains no node valued $3$ |
| State | The situation at a particular point, including the choices already made | The current path of visited nodes, stored in `path` |
| Trial | Exploring the solution space by making a choice, updating the state, and checking for a solution | Recursively visit a left or right child, append it to `path`, and check whether its value is $7$ |
| Backtracking | Undoing an earlier choice and returning to the previous state when a constraint cannot be met | Stop searching and return after passing a leaf, finishing a node, or encountering value $3$ |
| Pruning | Using problem properties and constraints to avoid pointless search paths and improve efficiency | Stop searching when a node valued $3$ is encountered |
!!! tip
Concepts such as problems, solutions, and states are shared by divide and conquer, backtracking, dynamic programming, greedy algorithms, and other approaches.
## Strengths and limitations
Backtracking is fundamentally a depth-first search that tries possible solutions until it finds ones meeting the conditions. It can find all possible solutions and can be highly efficient with effective pruning.
However, for large or complex problems, **backtracking may be unacceptably slow**.
- **Time**: Backtracking often explores every possibility in the state space, leading to exponential or factorial time complexity.
- **Space**: Recursive calls must preserve state, such as the path and auxiliary pruning variables. Deep recursion can therefore require substantial space.
Even so, **backtracking remains the best approach for some search and constraint satisfaction problems**. When we cannot predict which choices lead to valid solutions, we must explore every possibility. **The key is then to improve efficiency**, commonly through two techniques.
- **Pruning**: Avoid paths that cannot produce a solution, saving time and space.
- **Heuristic search**: Use strategies or estimates to explore paths most likely to yield valid solutions first.
## Typical backtracking problems
Backtracking can solve many search, constraint satisfaction, and combinatorial optimization problems.
**Search problems** seek solutions satisfying specified conditions.
- Permutations: Given a collection, find every possible ordering of its elements.
- Subset sum: Given a collection and a target sum, find every subset whose sum equals the target.
- Tower of Hanoi: Given three rods and disks of different sizes, move all disks from one rod to another, moving only one at a time and never placing a larger disk on a smaller one.
**Constraint satisfaction problems** seek solutions meeting all constraints.
- $n$-queens: Place $n$ queens on an $n \times n$ chessboard so that no two attack each other.
- Sudoku: Fill a $9 \times 9$ grid with digits $1$ ~ $9$ so that no digit repeats in any row, column, or $3 \times 3$ subgrid.
- Graph coloring: Color an undirected graph's vertices with as few colors as possible, ensuring adjacent vertices have different colors.
**Combinatorial optimization problems** seek an optimal solution satisfying conditions within a space of combinations.
- 0-1 knapsack: Given items with values and weights and a knapsack, choose items to maximize total value within the capacity limit.
- Traveling salesperson: Starting at a vertex in a graph, visit every other vertex exactly once and return to the start along the shortest possible route.
- Maximum clique: Given an undirected graph, find its largest complete subgraph, in which every pair of vertices is connected by an edge.
For many combinatorial optimization problems, backtracking is not the best approach.
- 0-1 knapsack is usually solved more efficiently with dynamic programming.
- The traveling salesperson problem is a well-known NP-hard problem. Common approaches include genetic algorithms and ant colony algorithms.
- Maximum clique is a classic graph theory problem that can be approached with heuristics such as greedy algorithms.