Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT
Advanced+200 XP

Unit 18 — Backtracking

Try a path. Hit a dead end. Undo. Try another. Backtracking is how computers solve puzzles — N-Queens, Sudoku, mazes, and subset problems. Brute force made smart by pruning impossible paths early.

90 min March 2026
UNIT 18Prerequisite: Unit 08 — Recursion90 min read

Some problems cannot be solved by a formula, a greedy choice, or a simple DP table. They require exploration — trying combinations until you find one that works. Backtracking is the systematic way to do this exploration without wasting effort on paths that are guaranteed to fail.

Think of it as navigating a maze. You walk forward until you hit a wall, then step back and try a different direction. You keep doing this until you either find the exit or prove there is no way through. Backtracking is exactly this — recursion plus an undo step.

// Section 1

What is Backtracking?

Backtracking is a recursive algorithm that builds a solution incrementally, one step at a time. At each step it tries all possible choices. If a choice leads to a valid partial solution it continues. If it leads to a dead end — a point where no valid completion exists — it undoes that choice (backtracks) and tries the next alternative.

Choose
Pick a candidate for the current position. Add it to your partial solution.
🔍
Explore
Recurse — try to build the rest of the solution on top of this choice.
↩️
Unchoose
If the recursion fails (dead end), remove your choice. The state goes back to what it was before. Then try the next candidate.
Universal backtracking template — every backtracking problem follows this skeleton:
c
void backtrack(state, choices[]) {
if (is_solution(state)) {
record or print the solution;
return;
}
for each choice in choices[] {
if (is_valid(state, choice)) {
make(choice); /* add choice to state */
backtrack(state, remaining_choices);
undo(choice); /* remove choice from state */
}
}
}
The undo(choice) step is what makes it backtracking — not just recursion. Without undo, choices accumulate and corrupt future attempts. With undo, the state is always clean when you try the next option.
💡 Note
Backtracking vs Brute Force: Pure brute force tries every possible combination regardless of validity. Backtracking prunes — it stops exploring a path the moment it becomes impossible. This pruning is called a constraint check and is what makes backtracking practical where brute force would be impossibly slow.
// Problem 1
Problem 01N-Queens — Place N Queens With No Attacks
Time: O(N!)Space: O(N)

Place N chess queens on an N×N board such that no two queens attack each other. Two queens attack if they share the same row, column, or diagonal. For N=4 there are 2 solutions. For N=8 there are 92 solutions. This is the most famous backtracking problem in computer science.

// One valid solution for N=4 — queens at columns [1,3,0,2]
queens at col: [1, 3, 0, 2]
Constraints checked at each placement:
No two in same column
col[i] ≠ col[j] for all i≠j
No two in same row
Only one queen per row (place one per row)
No two on same diagonal ↘
|row_i - row_j| ≠ |col_i - col_j|
// Partial trace — placing queens row by row for N=4
Row 0: try col 0 → place queen at (0,0)
Row 1: try col 0 → same col → skip
Row 1: try col 1 → diagonal attack → skip
Row 1: try col 2 → place queen at (1,2)
Row 2: all columns blocked → BACKTRACK
Row 1: try col 3 → place queen at (1,3)
Row 2: try col 1 → place queen at (2,1)
Row 3: try col 0,1,2 blocked → col 3 blocked → BACKTRACK
Row 2: ... continue until (2,0) → (3,2) → SOLUTION! [0,3,1,2... wait checking]
Eventually finds: cols = [1, 3, 0, 2] ✓
c
#include <stdio.h>
#include <stdlib.h>
int N;
int queens[20]; /* queens[row] = column where queen is placed in that row */
int solutions = 0;
/* check if placing a queen at (row, col) is safe */
int isSafe(int row, int col) {
int i;
for (i = 0; i < row; i++) {
/* same column */
if (queens[i] == col) return 0;
/* same diagonal — difference in rows equals difference in cols */
if (abs(queens[i] - col) == abs(i - row)) return 0;
}
return 1; /* safe to place */
}
void printBoard() {
int r, c;
printf("Solution %d:\n", solutions);
for (r = 0; r < N; r++) {
for (c = 0; c < N; c++) {
printf("%s ", queens[r] == c ? "Q" : ".");
}
printf("\n");
}
printf("\n");
}
/* place queens row by row using backtracking */
void solveNQueens(int row) {
/* base case: all N queens placed successfully */
if (row == N) {
solutions++;
printBoard();
return;
}
int col;
for (col = 0; col < N; col++) {
if (isSafe(row, col)) {
queens[row] = col; /* CHOOSE: place queen */
solveNQueens(row + 1); /* EXPLORE: try next row */
queens[row] = -1; /* UNCHOOSE: remove queen (backtrack) */
}
}
}
int main() {
N = 4;
int i;
for (i = 0; i < N; i++) queens[i] = -1;
solveNQueens(0);
printf("Total solutions for N=%d: %d\n", N, solutions); /* 2 */
/* Try N=8 */
N = 8; solutions = 0;
for (i = 0; i < N; i++) queens[i] = -1;
solveNQueens(0);
printf("Total solutions for N=%d: %d\n", N, solutions); /* 92 */
return 0;
}
Time:O(N!)(pruning makes it much faster in practice)
Space:O(N)(queens array + call stack)
// Problem 2
Problem 02Rat in a Maze — Find a Path Through a Grid
Time: O(2^(N²))Space: O(N²)

Given an N×N grid where 1 = open cell and 0 = blocked wall, find a path from the top-left corner (0,0) to the bottom-right corner (N-1, N-1). The rat can move right or down only. Print the solution path. Classic backtracking — try a direction, if blocked backtrack and try another.

// 4×4 maze — 1=open, 0=wall. Find path from (0,0) to (3,3)
Input maze
1
0
0
0
1
1
0
1
0
1
0
0
1
1
1
1
Solution path (1=visited)
·
·
·
·
·
·
·
·
·
Path: (0,0) → (1,0) → (1,1) → (2,1) → (3,1) → (3,2) → (3,3) ✓
c
#include <stdio.h>
#define N 4
int maze[N][N] = {
{1, 0, 0, 0},
{1, 1, 0, 1},
{0, 1, 0, 0},
{1, 1, 1, 1}
};
int solution[N][N]; /* stores the path — 1 = part of path */
int isSafeCell(int row, int col) {
return (row >= 0 && row < N &&
col >= 0 && col < N &&
maze[row][col] == 1 &&
solution[row][col] == 0); /* not already visited */
}
void printSolution() {
int i, j;
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
printf("%d ", solution[i][j]);
}
printf("\n");
}
printf("\n");
}
int solveMaze(int row, int col) {
/* base case: reached destination */
if (row == N-1 && col == N-1) {
solution[row][col] = 1;
return 1; /* found a path */
}
if (isSafeCell(row, col)) {
solution[row][col] = 1; /* CHOOSE: mark cell as visited */
/* EXPLORE: try moving down first */
if (solveMaze(row + 1, col)) return 1;
/* EXPLORE: try moving right */
if (solveMaze(row, col + 1)) return 1;
/* UNCHOOSE: neither direction worked — backtrack */
solution[row][col] = 0;
return 0;
}
return 0; /* this cell is not safe */
}
int main() {
int i, j;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
solution[i][j] = 0;
if (solveMaze(0, 0)) {
printf("Path found:\n");
printSolution();
} else {
printf("No path exists\n");
}
return 0;
}
🎯 Pro Tip
Extending to all 4 directions: The code above only moves right or down. To allow all 4 directions (up, down, left, right), add two more recursive calls: solveMaze(row-1, col) and solveMaze(row, col-1). The visited check in isSafeCell prevents infinite loops.
// Problem 3
Problem 03Sudoku Solver — Fill the Grid
Time: O(9^m)Space: O(1)

Fill a 9×9 grid with digits 1-9 such that every row, every column, and every 3×3 subgrid contains each digit exactly once. Empty cells are represented by 0. m is the number of empty cells. This is the most complex backtracking problem we cover — but the code structure is identical to N-Queens.

// Partial sudoku grid — 0 = empty cell to fill
5
3
·
·
7
·
·
·
·
6
·
·
1
9
5
·
·
·
·
9
8
·
·
·
·
6
·
8
·
·
·
6
·
·
·
3
4
·
·
8
·
3
·
·
1
7
·
·
·
2
·
·
·
6
·
6
·
·
·
·
2
8
·
·
·
·
4
1
9
·
·
5
·
·
·
·
8
·
·
7
9
Green cells = empty (to be filled by backtracking)
c
#include <stdio.h>
int board[9][9] = {
{5,3,0,0,7,0,0,0,0},
{6,0,0,1,9,5,0,0,0},
{0,9,8,0,0,0,0,6,0},
{8,0,0,0,6,0,0,0,3},
{4,0,0,8,0,3,0,0,1},
{7,0,0,0,2,0,0,0,6},
{0,6,0,0,0,0,2,8,0},
{0,0,0,4,1,9,0,0,5},
{0,0,0,0,8,0,0,7,9}
};
/* check if placing num at (row, col) is valid */
int isValid(int row, int col, int num) {
int i, j;
/* check the row */
for (i = 0; i < 9; i++) {
if (board[row][i] == num) return 0;
}
/* check the column */
for (i = 0; i < 9; i++) {
if (board[i][col] == num) return 0;
}
/* check the 3x3 subgrid */
int startRow = row - row % 3; /* top-left row of this subgrid */
int startCol = col - col % 3; /* top-left col of this subgrid */
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
if (board[startRow + i][startCol + j] == num) return 0;
}
}
return 1; /* valid placement */
}
void printBoard() {
int i, j;
for (i = 0; i < 9; i++) {
if (i % 3 == 0 && i != 0) printf("------+-------+------\n");
for (j = 0; j < 9; j++) {
if (j % 3 == 0 && j != 0) printf("| ");
printf("%d ", board[i][j]);
}
printf("\n");
}
}
int solveSudoku() {
int row, col, num;
/* find the next empty cell */
for (row = 0; row < 9; row++) {
for (col = 0; col < 9; col++) {
if (board[row][col] == 0) {
/* try digits 1 to 9 */
for (num = 1; num <= 9; num++) {
if (isValid(row, col, num)) {
board[row][col] = num; /* CHOOSE */
if (solveSudoku()) return 1; /* EXPLORE — solved! */
board[row][col] = 0; /* UNCHOOSE — backtrack */
}
}
return 0; /* no valid digit found — backtrack further */
}
}
}
return 1; /* no empty cells left — puzzle solved */
}
int main() {
printf("Solving sudoku...\n\n");
if (solveSudoku()) {
printBoard();
} else {
printf("No solution exists\n");
}
return 0;
}
Time:O(9^m)(m = empty cells, pruning makes it fast)
Space:O(1)(modifies board in-place)
// Problem 4
Problem 04Subset Sum — Find All Subsets That Sum to Target
Time: O(2^n)Space: O(n)

Given a set of numbers and a target sum, find all subsets that add up to the target. Set: {3, 1, 4, 2}, Target: 5. Valid subsets: {3,2} and {1,4}. This generalises to many real problems — portfolio selection, partition problems, resource allocation.

c
#include <stdio.h>
int arr[] = {3, 1, 4, 2};
int n = 4;
int target = 5;
int subset[10];
int subsetSize = 0;
int solutionsFound = 0;
void printSubset() {
int i;
printf(" { ");
for (i = 0; i < subsetSize; i++) printf("%d ", subset[i]);
printf("}\n");
}
void findSubsets(int index, int currentSum) {
/* base case: found a valid subset */
if (currentSum == target) {
solutionsFound++;
printSubset();
return;
}
/* pruning: sum already exceeds target — stop this path */
if (currentSum > target || index == n) return;
/* CHOICE 1: include arr[index] */
subset[subsetSize++] = arr[index]; /* CHOOSE */
findSubsets(index + 1, currentSum + arr[index]); /* EXPLORE */
subsetSize--; /* UNCHOOSE */
/* CHOICE 2: exclude arr[index] */
findSubsets(index + 1, currentSum); /* EXPLORE without this element */
}
int main() {
printf("Subsets of {3,1,4,2} that sum to %d:\n", target);
findSubsets(0, 0);
printf("Total: %d subset(s) found\n", solutionsFound);
/* Output:
{ 3 2 }
{ 1 4 }
Total: 2 subset(s) found
*/
return 0;
}
The decision tree — at each element, include or exclude:
start(sum=0)
├── include 3 (sum=3)
│ ├── include 1 (sum=4)
│ │ ├── include 4 (sum=8 > 5) → PRUNE
│ │ └── include 2 (sum=6 > 5) → PRUNE
│ └── exclude 1 (sum=3)
├── include 4 (sum=7 > 5) → PRUNE
└── include 2 (sum=5) → FOUND {3,2}
└── exclude 3 (sum=0)
├── include 1 (sum=1)
│ ├── include 4 (sum=5) → FOUND {1,4}
│ └── ...
└── ...
// Problem 5
Problem 05Generate All Permutations of a String
Time: O(n × n!)Space: O(n)

Generate all possible arrangements of characters in a string. "ABC" has 6 permutations: ABC, ACB, BAC, BCA, CAB, CBA. Used in: password recovery, anagram generation, combinatorics problems.

c
#include <stdio.h>
#include <string.h>
void swap(char *a, char *b) {
char t = *a; *a = *b; *b = t;
}
void permutations(char str[], int start, int end) {
/* base case: single character remaining — print the permutation */
if (start == end) {
printf("%s\n", str);
return;
}
int i;
for (i = start; i <= end; i++) {
swap(&str[start], &str[i]); /* CHOOSE: bring char i to front */
permutations(str, start + 1, end); /* EXPLORE: permute the rest */
swap(&str[start], &str[i]); /* UNCHOOSE: restore original order */
}
}
int main() {
char str[] = "ABC";
int n = strlen(str);
printf("All permutations of '%s':\n", str);
permutations(str, 0, n - 1);
/* Output:
ABC ACB BAC BCA CBA CAB
*/
return 0;
}
// Section 6

Pruning — The Key to Making Backtracking Practical

Without pruning, backtracking is just brute force — trying every possible combination. Pruning is when you detect early that a partial solution cannot possibly lead to a valid complete solution and cut off the entire subtree of possibilities without exploring it. Good pruning can reduce an exponential search to something practical.

N-Queens
Prune when: If a column, row, or diagonal is already occupied, skip that position entirely — do not recurse
Impact: For N=8: reduces 8⁸ = 16M positions to just 92 solutions explored in thousands of checks
Subset Sum
Prune when: If current sum already exceeds target, stop — adding more positive numbers will never bring it back down
Impact: With sorted input + pruning, eliminates most branches for large inputs
Sudoku
Prune when: If a digit already appears in the same row, column, or box — skip it. This eliminates most candidates immediately
Impact: A hard sudoku typically requires only a few hundred recursive calls with pruning vs millions without
💡 Note
The best pruning strategy: Check constraints as early as possible — at the moment you make a choice, before recursing. Never enter a subtree that you can already prove is invalid. The earlier you prune, the more you save because each pruned node eliminates all its descendants.
// Section 7

Backtracking vs Other Approaches

ApproachHow it worksWhen to useExample
GreedyPick best option now, never undoSingle optimal choice at each stepActivity selection, Huffman
DPStore all subproblem answersOverlapping subproblems, optimal valueKnapsack, LCS, coin change
BacktrackingTry all, undo on failure, prune earlyConstraints, enumerate solutionsN-Queens, Sudoku, subsets
BFS/DFSTraverse graph systematicallyReachability, shortest pathMaze, connected components
// Errors You Will Hit

Common Backtracking Mistakes

Forgetting to undo the choice (missing backtrack step)
Symptom: Partial solutions accumulate — later attempts start with corrupted state and produce wrong results
Fix: Every make(choice) must have a corresponding undo(choice) after the recursive call returns. They always come in pairs.
Not checking visited cells in maze/grid problems
Symptom: Infinite recursion — the rat walks in circles forever and the program crashes with stack overflow
Fix: Mark a cell as visited before recursing. Check isSafe includes a visited check. Unmark when backtracking.
Missing pruning — valid check too late
Symptom: Correct results but takes forever — exploring millions of invalid combinations
Fix: Check constraints BEFORE recursing, not after. The constraint check should be the first thing inside the loop — skip the candidate immediately if it violates a rule.
// What's Next

You Are Ready for Unit 19

You now understand backtracking completely — the choose-explore-unchoose pattern, five classic problems, pruning strategies, and the mistakes to avoid. Backtracking + pruning is how every Sudoku app, chess engine, and constraint solver you have ever used works under the hood.

In Unit 19 — the final unit — we cover Advanced Topics: Segment Trees, Fenwick Trees (BIT), Tries, Union-Find (DSU), Sliding Window, Two Pointers, and Bit Manipulation. These are the techniques that separate good engineers from great ones in FAANG and product company interviews.

UP NEXT → UNIT 19
Advanced Topics — The Final Level
Segment Tree, Trie, DSU, Sliding Window, Two Pointers, Bit Manipulation — all in C.
Coming Soon →

🎯 Key Takeaways

  • Backtracking = recursion + undo. Try a choice, explore, and if it fails undo that choice and try the next one
  • Three phases: Choose (make a decision), Explore (recurse with that decision), Unchoose (undo before trying next)
  • Every make(choice) must be paired with an undo(choice) after the recursive call — always in pairs
  • N-Queens: place queens row by row, check column and diagonal safety before placing, backtrack when blocked
  • Rat in a Maze: mark cell visited before recursing, unmark on backtrack, check bounds and walls
  • Sudoku: find empty cell, try digits 1-9, check row+column+box validity, backtrack when no digit fits
  • Subset Sum: at each element choose include or exclude, prune when sum exceeds target
  • Permutations: swap current with each remaining, recurse on rest, swap back to restore
  • Pruning = detecting impossible paths early and stopping without exploring them — makes backtracking practical
  • Without pruning = brute force. With pruning = backtracking. The earlier you prune, the more you save
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...