Backtracking is one of the most powerful paradigms in DSA. It is widely used in interview questions (FAANG, MAANG, Tier-1 product companies) and competitive programming to solve problems involving searching, decision-making, and constraints.
In this blog, we’ll go step by step — intuition → template → example → variations.
🔹 What is Backtracking?
At its core, backtracking is brute force + pruning.
- We try out possible choices (explore).
- If the choice leads to an invalid solution → undo (backtrack).
- If the choice works → continue exploring deeper.
👉 Think of it as a DFS search on a decision tree.
🔹 Key Components of Backtracking
Every backtracking solution revolves around three parts:
- Choices – What options do we have at this step?
- Constraints – Which choices are valid? (Pruning)
- Goal – When do we stop recursion?
🔹 Universal Backtracking Template (Java)
void backtrack(State state, List<Result> results) { if (goalReached(state)) { results.add(new Result(state)); return; } for (Choice choice : validChoices(state)) { // 1. Make a choice state.make(choice); // 2. Explore further backtrack(state, results); // 3. Undo the choice (backtrack) state.undo(choice); } }
✨ This template is reusable for subsets, permutations, combinations, pathfinding, puzzles, CSP problems.
🔹 Example: N-Queens (Classic Backtracking)
📌 Problem: Place N queens on an N×N chessboard such that no two queens attack each other.
Step 1: Choices
- Place a queen in any column of the current row.
Step 2: Constraints
- No two queens should share the same column, diagonal, or anti-diagonal.
Step 3: Goal
- When all rows are filled → valid solution.
Java Code (N-Queens Solver)
import java.util.*; public class NQueens { public List<List<String>> solveNQueens(int n) { List<List<String>> results = new ArrayList<>(); char[][] board = new char[n][n]; for (char[] row : board) Arrays.fill(row, '.'); backtrack(0, board, results, n); return results; } private void backtrack(int row, char[][] board, List<List<String>> results, int n) { if (row == n) { results.add(construct(board)); return; } for (int col = 0; col < n; col++) { if (isSafe(board, row, col, n)) { // make choice board[row][col] = 'Q'; // explore backtrack(row + 1, board, results, n); // undo choice (backtrack) board[row][col] = '.'; } } } private boolean isSafe(char[][] board, int row, int col, int n) { // check column for (int i = 0; i < row; i++) if (board[i][col] == 'Q') return false; // check upper-left diagonal for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] == 'Q') return false; // check upper-right diagonal for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] == 'Q') return false; return true; } private List<String> construct(char[][] board) { List<String> res = new ArrayList<>(); for (char[] row : board) res.add(new String(row)); return res; } }
🔹 Recursion Tree Visualization
For N=4
:
- Row 0 → Try placing queen at col
0..3
- Row 1 → Recurse only where previous placement is safe
- Row 2 → Same logic
- Row 3 → If reached → add solution
The recursion explores possibilities, prunes invalid ones, and backtracks to try other paths.
🔹 Complexity Analysis
- Worst-case: O(N!) (factorial explosion in permutations).
- With pruning: significantly reduced search space.
- Space: O(N²) for board, O(N) recursion depth.
🔹 Key Takeaways
✅ Backtracking = DFS + Undo
✅ Always define Choice, Constraint, Goal
✅ Use the universal template for different problem types
✅ Pruning makes brute force efficient enough for interviews
🔹 What’s Next?
In Blog 2: Subset & Power Set Pattern, we’ll explore how backtracking generates subsets of a set, power sets, and their interview variations.
Top comments (0)