Skip to main content

Backtracking

Backtracking is an algorithmic paradigm used to solve problems in which the number of possible candidate solutions is very large, but many of those candidates can be discarded early because they violate some constraint. The central idea is simple: construct a solution step by step, and as soon as a partial solution cannot possibly lead to a valid complete solution, abandon it and return to the previous step.

This paradigm is especially important in combinatorial problems, where the number of possible configurations grows very quickly and brute force becomes impractical. In computer science, a combinatorial problem is one whose solutions are formed by selecting, arranging, assigning, or combining elements from a finite set, usually under constraints. What makes these problems difficult is not the cost of a single operation, but the explosive growth of the search space as the input size increases.

At first glance, backtracking may seem similar to brute force, because both techniques explore many possibilities. However, brute force usually generates complete candidates first and checks them only at the end, while backtracking checks validity during construction and prunes invalid branches as soon as they appear.

A brute-force algorithm explores the search space almost blindly: it may spend a lot of time building full solutions that were already doomed to fail from the beginning.

A backtracking algorithm builds the candidate incrementally, one decision at a time, and as soon as a partial assignment violates a constraint or cannot be extended into a valid solution, the algorithm prunes that branch immediately. This early rejection is what makes backtracking more efficient than naive exhaustive search in many practical cases.

So the difference can be summarized as:

  • Brute force: generate everything first, validate later.
  • Backtracking: validate progressively, prune early.

That is why backtracking is especially suitable for combinatorial problems: it still explores a large search space, but it avoids wasting time on candidates that are already impossible.


Why brute force is not enough

In many algorithmic problems, the number of possible configurations grows so fast that generating them all is not realistic. This phenomenon is called combinatorial explosion: a small increase in input size can produce an enormous increase in the number of candidate solutions.

Consider the classic Magic Square problem. We want to fill an n×nn \times n square with the numbers from 1 to n2n^2, without repetition, so that every row, every column, and both diagonals have the same sum.

If we solve it by naive brute force, the number of possible arrangements becomes huge very quickly:

  • For n=3n = 3, there are 9!=362,8809! = 362{,}880 possible arrangements.
  • For n=5n = 5, there are 25!1.55×102525! \approx 1.55 \times 10^{25} possible arrangements.

This means that even a moderately sized instance can generate a search space that is far beyond what can be explored exhaustively in practice. And this is only counting the number of arrangements; in a real problem, each arrangement also has to be checked against the constraints, which adds even more work.

The key idea is that not every partial arrangement is worth exploring. In a magic square, for example, once a row sum, column sum, or diagonal sum already makes a valid final solution impossible, there is no reason to continue extending that candidate. Backtracking takes advantage of exactly this fact: it detects impossible partial assignments as soon as they appear and stops exploring that branch immediately.

In other words, brute force wastes time exploring configurations that were doomed from the start, while backtracking avoids that waste by pruning the search tree early.

Visual intuition

Magic Square search space

Brute force:
[ Start ]
|
┌───────────────┼───────────────┐
▼ ▼ ▼
[ Choice A ] [ Choice B ] [ Choice C ]
| | |
▼ ▼ ▼
[ A1, A2 ] [ B1, B2 ] [ C1, C2 ]
| | |
▼ ▼ ▼
[ Full candidate ] [ Full candidate ] [ Full candidate ]
| | |
X ✓ X

Backtracking:
[ Start ]
|
┌───────────────┼───────────────┐
▼ ▼ ▼
[ Choice A ] [ Choice B ] [ Choice C ]
| | |
X ▼ X
|
[ B1, B2 ]
|

In the first case, every branch is expanded until a complete candidate is built, and only then is it checked. In the second case, invalid branches are discarded immediately, so the algorithm spends its effort only on promising candidates.

Brute force follows the rule generate everything first, test later.
Backtracking follows the rule test early, prune immediately.

That difference is what makes backtracking much more efficient in combinatorial problems where constraints can be checked incrementally.


To understand backtracking more precisely, it is useful to view the problem as a search through a space of states. This perspective makes it easier to formalize how the algorithm explores partial solutions, how it generates new candidates, and why invalid branches can be discarded early.

Backtracking is usually understood as a state-space search technique. In this framework, each problem configuration is modeled as a state, and solving the problem means moving through a space of states until a goal state is found. A state can represent a complete solution, a partial solution, or an intermediate configuration that may still be extended.

A state-space model can be interpreted as a graph:

  • Nodes represent states or partial configurations of the problem.
  • Edges represent valid transitions from one state to another.

The algorithm begins at an initial state and explores possible transitions until it reaches one or more solution states. If a state violates the problem constraints, or if it is clear that no valid solution can be reached from it, that branch is abandoned and the search returns to the previous decision point.

Explicit graph vs implicit graph

For many real problems, it is impossible or inefficient to build the entire graph in memory before searching it. The graph may simply be too large, or it may not even be practical to enumerate all its states in advance.

That is why backtracking typically works on an implicit graph:

  • The graph is not stored explicitly in memory.
  • When the algorithm is at a state, it generates only the child nodes of that state.
  • Once a branch has been explored, it can be discarded and the algorithm continues elsewhere.

This is different from an explicit graph, where all nodes and edges are constructed and stored before the search begins. In an explicit graph, the whole structure must exist in memory, which can be expensive or impossible for large combinatorial problems. In contrast, an implicit graph generates states only when they are needed, so the algorithm avoids storing branches that will never be explored.

This gives two major advantages:

  1. Lower memory usage, because only the current path and its local information must be stored.
  2. Potential time savings, because many branches are never explored at all if they are pruned early.

This perspective is one of the reasons backtracking is so effective in combinatorial problems: instead of building the full search space, it explores it gradually and only keeps the parts that may still lead to a valid solution.


Tree of states

Backtracking can be understood as a search through a space of possible states. Although this space may be represented abstractly as a graph, in introductory algorithmic settings it is usually more helpful to visualize it as a tree of states. This tree is not a literal data structure stored in memory, but a conceptual model of how the algorithm explores the problem.

Each node in the tree represents a state of the problem:

  • The root corresponds to the initial state.
  • Each internal node represents a partial solution.
  • Each child represents one valid choice or decision that extends the current partial solution.
  • A leaf may represent either a complete solution or a dead end.

Each level of the tree corresponds to one decision step. The deeper a node is, the more decisions have already been fixed. This is why backtracking is usually a constructive process: the algorithm does not jump directly to complete solutions, but builds them incrementally, one decision at a time.

[ Root: Initial State ]

┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
[ Choice X₁ ] [ Choice Y₁ ] [ Choice Z₁ ]
│ │ │
▼ ▼ ▼
[ Choice X₂ ] [ Choice Y₂ ] (Invalid State)
│ │ │
▼ ▼ [ Prune & Backtrack ]
(Solution Leaf) (Invalid State) ▲
▲ │ │
[ Return Solution ] [ Prune Branch ] ──────────┘

The algorithm explores this tree depth first. It selects one branch, follows it as far as possible, and only when that branch can no longer lead to a valid solution does it return to the most recent decision point and try another alternative. This strategy is known as Depth-First Search (DFS).

DFS fits backtracking very well because the algorithm only needs to remember the current path from the root to the current node. The recursive call stack is usually enough to keep track of that path, which makes backtracking memory efficient:

  • it does not need to store the entire search tree,
  • it only keeps the active branch,
  • and it discards branches as soon as they are proven invalid.

Partial solutions and decision levels

A backtracking algorithm usually builds a candidate solution incrementally. If the final solution has nn decision variables, we can represent it as:

(x1,x2,,xn)(x_1, x_2, \dots, x_n)

where each variable xix_i corresponds to one decision that must be made during the search.

At an intermediate step, the algorithm has only constructed a partial solution:

(x1,x2,,xk)(x_1, x_2, \dots, x_k)

with k<nk < n. This means that the first kk decisions have already been fixed, but the remaining variables are still open.

At level kk, the algorithm tries to assign a valid value to xk+1x_{k+1}. Each valid choice creates a child node in the state tree. If the new partial solution remains consistent with the problem constraints, the search continues to level k+1k+1. If the assignment violates a constraint, the branch is discarded immediately.

This is the heart of backtracking: the algorithm does not wait until the entire vector is built to detect an error; it rejects invalid partial solutions as soon as they appear. By doing so, it avoids exploring branches that cannot possibly lead to a valid complete solution.

The rollback mechanism

The term backtracking refers precisely to the process of going back after a failed attempt. When a partial solution stops being promising, the algorithm returns to the most recent decision point and tries a different alternative.

A branch of the search can end in two ways:

  • Success: a complete valid solution has been constructed.
  • Failure: the current partial solution cannot be extended into any valid complete solution.

When failure occurs, the algorithm must undo the most recent choice, restore the previous state, and continue with the next available option. This is the characteristic choose, explore, undo pattern of backtracking algorithms.

In recursive implementations, rollback usually happens naturally when a recursive call returns and the algorithm resumes execution at the previous level of the search tree. In iterative terms, this is equivalent to removing the last decision from the current partial solution and continuing with the next candidate.

This rollback step is essential because it allows the same partial prefix to be reused while exploring several alternatives. Without it, the algorithm would not be able to systematically traverse the full search space.

In summary, the tree of states explains the structure of the search, partial solutions explain how the candidate is built, and rollback explains how the algorithm moves from one branch to another.


Backtracking as DFS with pruning

It is common to describe backtracking as DFS with pruning, and this is a useful first intuition. In backtracking, DFS determines the order in which the search space is explored, while pruning determines which branches are discarded without being explored completely.

However, backtracking is more than just a traversal strategy. It is a problem-solving paradigm that combines several ideas:

  • incremental construction of a candidate solution,
  • depth-first exploration of the state space,
  • early constraint checking,
  • rollback when a branch fails,
  • and pruning of impossible branches.

This distinction matters because not every DFS algorithm is backtracking. A plain DFS traversal may simply visit nodes in depth-first order, while a backtracking algorithm also builds partial solutions, checks feasibility at each step, and undoes choices when necessary.

For this reason, backtracking is best understood as a DFS-based search process specialized for combinatorial problems with constraints.


When backtracking is a good idea

Backtracking is particularly appropriate when the problem can be naturally described as a sequence of decisions and when those decisions can be checked against constraints before a complete solution is built. In other words, it is a good choice when the solution space is large, but many candidate branches can be eliminated early.

This makes backtracking especially useful in problems where:

  • the solution is built incrementally,
  • partial solutions can be validated as soon as they are generated,
  • the number of possible combinations grows very quickly,
  • and pruning can eliminate a large part of the search space.

Backtracking is a good idea in several common situations.

1. When the problem asks for one valid solution

Sometimes the goal is not to find all solutions, but simply to find any solution that satisfies the constraints. In these cases, backtracking can stop as soon as it finds the first valid answer. This can make it much more efficient than brute force, because the algorithm does not need to explore the entire search space once a correct solution has already been found.

Examples include:

  • finding one valid arrangement of queens in the N-Queens problem,
  • solving a Sudoku puzzle,
  • finding a path that satisfies certain restrictions in a graph.

In this kind of problem, backtracking is useful because it searches systematically, but it can terminate early once success has been reached.

2. When the problem asks for all valid solutions

In other cases, the objective is to enumerate every possible solution that satisfies the constraints. Here, backtracking is especially natural because it can explore the entire search space while discarding invalid branches as early as possible.

Examples include:

  • listing all valid permutations or combinations that satisfy a condition,
  • generating all subsets with a given property,
  • enumerating all valid colorings of a graph,
  • producing all solutions to a puzzle.

This is one of the classic strengths of backtracking: it can systematically generate all feasible answers without wasting effort on branches that are already impossible.

3. When constraints can be checked early

Backtracking is most effective when the problem includes constraints that can be evaluated on a partial solution, not only on a complete one. If the algorithm can detect a violation early, it can prune the branch immediately and avoid exploring it further.

For example:

  • in N-Queens, two queens attacking each other makes the branch invalid right away;
  • in Sudoku, placing a number that already appears in the same row, column, or subgrid is enough to reject that candidate;
  • in subset sum, if the partial sum already exceeds the target, the branch can be discarded.

The earlier the constraints can be checked, the more effective backtracking becomes.

4. When the search space is large but structured

Backtracking is a strong option when the number of possible candidates is enormous, but the problem has enough structure to guide the search. In these cases, brute force would be too expensive, but backtracking can still be practical because it avoids exploring large portions of the tree.

This is typical in combinatorial problems, where the number of possible configurations grows rapidly with the input size. Backtracking does not remove the combinatorial nature of the problem, but it can dramatically reduce the amount of work by pruning useless branches.

5. When the problem is a constraint satisfaction problem

Backtracking is a classical technique for constraint satisfaction problems (CSPs). In a CSP, the task is to assign values to variables while respecting a set of restrictions. This is exactly the kind of setting where backtracking fits naturally.

Typical examples include:

  • N-Queens,
  • graph coloring,
  • Sudoku,
  • scheduling and assignment problems.

In these problems, each variable assignment is a decision, and each new decision must preserve consistency with the constraints already imposed. If consistency is lost, the algorithm goes back and tries another option.

6. When the problem is an enumeration problem

Backtracking is also very suitable when the objective is to enumerate solutions rather than just find one. Since the algorithm already explores the search tree systematically, it can be adapted to collect every valid result.

This is useful in:

  • permutation generation,
  • combination generation,
  • subset generation,
  • puzzle solving where all solutions are needed.

In this setting, backtracking acts as a controlled exhaustive search: it still explores all valid answers, but it avoids expanding branches that are impossible from the beginning.

7. When the problem is an optimization problem with pruning opportunities

Backtracking can also be used in optimization problems, although in those cases it is often combined with additional pruning rules or bounding techniques. The idea is to search for the best feasible solution, but stop exploring branches that cannot improve the current best one.

This is common in:

  • route planning,
  • resource allocation,
  • scheduling,
  • combinatorial optimization problems.

If the algorithm can estimate a bound on the best possible result from a partial solution, then branches that cannot outperform the current best solution can be discarded. This is one of the main ideas behind branch-and-bound, which is closely related to backtracking.

When backtracking is not a good idea

Backtracking is not always the best choice. It is usually a poor fit when:

  • the problem has too many solutions and almost no pruning is possible,
  • partial solutions cannot be checked early,
  • the same subproblems are solved repeatedly, making dynamic programming better,
  • or the problem is large enough that even a pruned search tree remains impractical.

In those situations, other paradigms such as dynamic programming, greedy algorithms, or heuristic search may be more appropriate.

In summary, backtracking is a good idea when the problem is combinatorial, structured, and constraint-driven, especially if invalid branches can be detected early and eliminated before they grow too large.


General scheme of a backtracking algorithm

Most backtracking algorithms follow the same conceptual structure. Although the details vary from problem to problem, the underlying logic is always the same: build a candidate solution incrementally, test it as soon as possible, and abandon it immediately if it becomes invalid.

A typical backtracking procedure starts from an empty or initial state and then repeatedly tries to extend it:

  1. Start from an empty or initial state.
  2. Check whether the current state is already a complete solution.
  3. If it is, process it as a valid answer and stop if the problem only asks for one solution.
  4. If it is not complete, generate the possible next choices.
  5. For each choice:
    • apply the choice to the current partial state,
    • test whether the new state is still valid,
    • if it is valid, continue recursively from that state,
    • once the recursive call finishes, undo the choice and restore the previous state.

In compact form, the pattern is usually summarized as:

  • Choose a candidate.
  • Explore the consequences recursively.
  • Undo the choice and try the next alternative.

This template is extremely general and appears in most classical examples, such as N-Queens, Sudoku, subset generation, graph coloring, and Hamiltonian path search. What changes from one problem to another is not the structure of the algorithm, but the meaning of the state, the set of available choices, and the constraints used to validate a partial solution.

What changes from one problem to another

Although the backtracking skeleton is always similar, each problem defines its own:

  • state representation,
  • set of possible choices,
  • feasibility test,
  • stopping condition,
  • and objective, if the problem is about optimization.

Common recursive templates

Backtracking algorithms are usually implemented with one of two recursive templates, depending on the objective of the search. The underlying search process is the same in both cases; what changes is whether the algorithm stops at the first valid answer or continues exploring the state space.

Use this version when the objective is to find any one valid solution. As soon as a valid configuration is found, the algorithm can stop immediately.

public class BacktrackingFirstSolution {
private boolean found = false;

public void solve(State initialState) {
found = backtracking(initialState);
if (!found) {
System.out.println("THERE IS NO SOLUTION");
}
}

private boolean backtracking(State state) {
if (isSolution(state)) {
System.out.println(state);
found = true;
return true;
}

for (State child : state.getChildren()) {
if (backtracking(child)) {
return true;
}
}

return false;
}
}

This pattern is especially useful when the problem only asks whether a solution exists, or when any valid configuration is acceptable. In that case, there is no need to explore the rest of the search tree once success has been reached.

Why two templates?

The first template is more efficient when only one solution is needed. The second template is necessary when the problem requires all solutions, a complete enumeration, or an optimization process over the full search space.


Pruning: the key to efficiency

Pruning is one of the main reasons why backtracking is much more effective than naive exhaustive search in practice. The basic idea is simple: if we can prove that a branch of the search tree can no longer lead to a valid or optimal solution, then there is no reason to keep exploring it.

This early elimination of impossible branches can dramatically reduce the size of the search space. Instead of generating every candidate and checking it at the end, backtracking stops as soon as a partial state becomes infeasible or clearly unpromising.

A branch should be pruned whenever continuing down that branch is useless. This may happen for several reasons:

  • the partial solution already violates a constraint;
  • the remaining decisions can no longer complete a valid solution;
  • or, in optimization problems, the current partial solution is already worse than the best known answer.

This last case is especially important in optimization settings. There, pruning is not only about rejecting invalid states, but also about discarding suboptimal states that cannot possibly improve the current best solution. In that sense, pruning is closely related to branch-and-bound.

Constraint-based pruning

The most common form of pruning is based on constraints. If a partial solution already breaks a rule, the branch is immediately discarded.

Examples:

  • N-Queens: if two queens attack each other, the current partial placement is invalid, so the branch is pruned.
  • Graph Coloring: if two adjacent vertices share the same color, the current coloring is invalid and should not be extended.
  • Sudoku: if a number is repeated in a row, column, or subgrid, that partial board is rejected immediately.

In all of these cases, the algorithm does not need to wait until a full solution is built. The violation is enough to stop exploration early.

Bound-based pruning

In optimization problems, we often maintain the best solution found so far. If a partial candidate cannot possibly beat that best solution, we can prune it even if it is still feasible.

Examples:

  • Subset Sum / Knapsack: if the current sum already exceeds the target, and all remaining values are non-negative, the branch can be discarded.
  • Traveling Salesman Problem: if the current partial tour already has a cost greater than the best known complete tour, there is no reason to continue that branch.

This kind of pruning is particularly powerful because it cuts off branches that are not invalid, but simply not worth pursuing.

Why early pruning matters

The earlier a problem allows us to prune invalid or unpromising states, the more effective backtracking becomes. Good pruning rules reduce the number of recursive calls, shorten the search, and often make problems that would otherwise be intractable much more manageable in practice.

In summary, pruning is the mechanism that transforms backtracking from a blind enumeration technique into a much more intelligent search strategy.


Worked intuition

Imagine a problem where you must make one decision at a time, and where not every combination of decisions is allowed. At each step, you choose one value, but the partial result may already be enough to know whether that branch is still worth exploring.

A brute-force method would generate all complete combinations first and only then check which ones are valid. This means it can waste a lot of time building full candidates that were already impossible from an earlier step.

A backtracking method works differently. It would:

  • choose the first value,
  • immediately check whether the partial choice is still consistent,
  • continue only if the current branch is still promising,
  • and undo the choice as soon as it leads to a dead end.

That is why backtracking is often described as systematic trial and error, but with intelligence. It explores possibilities in an organized way, while discarding bad branches as soon as they are recognized.


Complexity analysis

The running time of a backtracking algorithm depends mainly on two factors:

  • the number of nodes generated in the state tree,
  • and the amount of work needed to process each node.

A useful abstract model is:

Total Time=(Number of generated nodes)×(Cost per node)\text{Total Time} = (\text{Number of generated nodes}) \times (\text{Cost per node})

In many textbook problems, the work done at each node is small, often constant or close to constant. In that case, the dominant factor is the size of the search tree itself.

The important point is that backtracking does not avoid worst-case explosion in general: it reduces the search space in practice by pruning, but if very few branches can be discarded, the algorithm may still end up exploring an enormous number of states.

Exponential growth

If each state can generate up to mm children and the maximum depth is nn, then the number of explored nodes may grow exponentially:

T(n)=m+m2+m3++mnT(n) = m + m^2 + m^3 + \dots + m^n

This leads to:

T(n)=O(mn)T(n) = O(m^n)

A common special case is when each level offers roughly two choices. Then the complexity becomes:

T(n)=O(2n)T(n) = O(2^n)

This kind of growth is typical in problems where each decision has a small fixed number of alternatives, but the number of decisions is large.

Factorial growth

In some problems, the number of available choices decreases by one at each level. This is typical in permutation-like problems, where each new decision selects one element from the remaining unused elements.

In that case, the search may grow factorially:

T(n)=n+n(n1)+n(n1)(n2)++n!T(n) = n + n(n-1) + n(n-1)(n-2) + \dots + n!

which leads to:

T(n)=O(n!)T(n) = O(n!)

This happens because the algorithm is effectively exploring permutations of the input elements.

Why this matters

This is why backtracking is powerful but still potentially expensive. Pruning can dramatically reduce the number of states explored in practice, but in the worst case the algorithm may still need to visit an exponential or factorial number of nodes.

So, backtracking should be understood as a technique that improves practical efficiency by cutting off useless branches early, not as a method that eliminates combinatorial complexity altogether.


Space complexity

One of the main strengths of backtracking is that it usually uses much less memory than methods that store the entire search space.

The main memory cost comes from the recursion stack and from the information associated with the current partial solution. If the maximum depth of the recursion tree is hh and each recursive frame uses constant memory, then the auxiliary space required by the stack is:

Mtextstack=O(h)M_{\\text{stack}} = O(h)

This happens because backtracking explores one branch at a time. It does not need to keep all branches in memory simultaneously; it only stores the active path from the root to the current node, together with the local data needed to undo decisions when necessary.

In many classical problems, hh is proportional to the number of decisions in the final solution, so the extra memory is often linear in the depth of the search. This makes backtracking much more memory-efficient than exhaustive methods that explicitly generate and store every candidate.

However, this low space usage does not mean the algorithm is cheap overall: backtracking saves memory, but it may still require a very large amount of time in the worst case.


Backtracking vs Dynamic Programming

A common question in algorithm design is when to use backtracking and when to use dynamic programming. Although both techniques can be used to solve combinatorial problems, they are based on very different ideas.

As a general rule:

  • Backtracking explores a search tree and is useful when the goal is to construct valid solutions under constraints.
  • Dynamic Programming is usually preferable when the problem contains overlapping subproblems and optimal substructure, because it avoids recomputing the same states many times.

The key difference is that backtracking searches for feasible solutions by exploring possibilities and pruning invalid branches, while dynamic programming stores and reuses the results of subproblems. In other words, backtracking is about search, whereas dynamic programming is about reuse.

This distinction has important practical consequences:

  • If the problem asks for one valid solution or for all valid solutions, backtracking is often the natural choice.
  • If the problem asks for an optimal value and can be decomposed into repeated subproblems, dynamic programming is often more efficient.
  • If the problem has many constraints that can be checked incrementally, backtracking may be easier to formulate.
  • If the same states are solved many times, dynamic programming is usually superior.

Therefore, if a problem can be solved efficiently with dynamic programming, that solution is often preferable in practice. Backtracking is still essential, however, when the problem requires explicit enumeration, when constraints are naturally incremental, or when no efficient dynamic programming formulation is available.

In short:

  • Backtracking is a systematic search technique.
  • Dynamic Programming is a memorization-based optimization technique.

Backtracking vs brute force

Although the two methods are related, they should not be confused.

  • Brute force tries every possibility in a direct and usually unstructured way. It typically generates complete candidates and checks them only after the full solution has been constructed.
  • Backtracking also explores possibilities, but it does so incrementally and rejects bad partial candidates as soon as they become invalid.

The key difference is that brute force performs a nearly blind enumeration of the search space, while backtracking adds structure and early pruning. This means that backtracking does not merely try “all possibilities”; it tries them in a controlled order and stops exploring branches that cannot lead to a valid answer.

So backtracking can be seen as a more intelligent form of exhaustive search. It may still have the same worst-case asymptotic complexity as brute force, but in many practical instances it performs much better because large subtrees are never explored.

In short:

  • Brute force: generate everything, test later.
  • Backtracking: generate progressively, test early, and prune immediately.

Typical examples in Algorithms courses

Some classical problems where backtracking is taught and applied are:

Typical examples include:m

  • N-Queens.

  • Sudoku.

  • Subset Sum.

  • Graph Coloring.

  • Hamiltonian Cycle.

  • N-Queens: place nn queens on an ntimesnn \\times n board so that no two queens attack each other.

  • Subset Sum: determine whether a subset of numbers sums to a target value.

  • Graph Coloring: assign colors to the vertices of a graph using a limited number of colors without creating conflicts between adjacent vertices.

  • Hamiltonian Cycle: find a cycle that visits each vertex exactly once.

These problems are especially useful in an Algorithms course because they illustrate several core ideas of the backtracking paradigm in a very clear way:

  • Construction of partial solutions: the algorithm builds the answer step by step instead of trying to generate it all at once.
  • Feasibility checks: each new decision can be tested immediately to see whether it is still consistent with the constraints.
  • Pruning conditions: invalid or unpromising branches can be discarded early.
  • Different search objectives: depending on the problem, backtracking may be used to find one solution, all solutions, or the best feasible solution.

Taken together, these examples show why backtracking is a natural technique for combinatorial problems: the search space is large, but the constraints often allow many branches to be eliminated before they grow too far.


Self-check questions

Use these questions to verify that the core ideas are clear.

📌 Self-check 1: Why not brute force?

Question: Why is backtracking usually preferable to naive brute force in combinatorial problems?

Answer: Because backtracking checks constraints while the solution is being built and prunes branches that cannot lead to a valid answer, instead of generating every complete candidate first.

📌 Self-check 2: Nodes and edges

Question: In a state-space representation, what do nodes and edges represent?

Answer: Nodes represent states or partial configurations of the problem, and edges represent valid transitions or choices between states.

📌 Self-check 3: DFS and backtracking

Question: Are DFS and backtracking the same thing?

Answer: No. DFS is a traversal strategy, while backtracking is a problem-solving paradigm that usually uses DFS together with constraint checking, rollback, and pruning.

📌 Self-check 4: When do we backtrack?

Question: When does a backtracking algorithm return to a previous decision point?

Answer: It returns to a previous decision point when the current branch has either produced a complete solution or been proven unable to lead to a valid complete solution.

📌 Self-check 5: Backtracking or Dynamic Programming?

Question: If a problem admits both a backtracking solution and a Dynamic Programming solution, which one is usually preferred in practice?

Answer: Dynamic Programming is often preferred when applicable, because it avoids recomputation of overlapping subproblems and is usually more efficient.


Key ideas to remember

  • Backtracking explores a state-space tree.
  • It builds solutions incrementally, one decision at a time.
  • It usually follows a depth-first search (DFS) exploration order.
  • It uses pruning to discard impossible or unpromising branches early.
  • It is especially useful in constraint satisfaction and combinatorial search problems.
  • Its worst-case time complexity is often exponential or factorial, but its space usage is usually modest because it only stores the current path.

Suggested review prompt

Concept Consolidation

Review the following idea in your notes or with your study group:

Explain why backtracking is usually described as DFS over an implicit state-space tree. Then compare it with brute force and Dynamic Programming, and state when each technique is preferable.