Every recursive function needs:1.Basecase: when to stop(prevents infinite recursion)2.Recursivecase:break problem into smaller subproblem
3.Progress: each call must move toward the base case
Example
// JavaScript - Sum of array using recursionfunctionsumArray(arr, idx =0){if(idx >= arr.length)return0;// base casereturn arr[idx]+sumArray(arr, idx +1);// recursive case}// Power functionfunctionpower(base, exp){if(exp ===0)return1;// base caseif(exp %2===0){const half =power(base, exp /2);return half * half;// O(log n)}return base *power(base, exp -1);}
# Python
def sum_array(arr, idx=0):if idx >=len(arr):return0return arr[idx]+sum_array(arr, idx +1)
def power(base, exp):if exp ==0:return1if exp %2==0:
half =power(base, exp // 2)return half * half
return base *power(base, exp -1)
Output
sum_array([1,2,3,4]) → 10
power(2, 10) → 1024
Note Always identify the base case first. Stack overflow risk: Python default limit is 1000 frames (sys.setrecursionlimit can increase). For deep recursion, convert to iteration. The power function shows how recursion can achieve O(log n) - a common follow-up.
base caserecursive caserecursion fundamentalsstack overflowrecursive thinking
Permutations
Syntax
Generate all orderings of elements.Backtrack: at each position,try each unused element.Swap or use a 'used' boolean array to track which elements are placed.
Example
// JavaScriptfunctionpermute(nums){const result =[];functionbacktrack(current, remaining){if(remaining.length===0){
result.push([...current]);return;}for(let i =0; i < remaining.length; i++){
current.push(remaining[i]);backtrack(current,[...remaining.slice(0, i),...remaining.slice(i +1)]);
current.pop();// undo choice}}backtrack([], nums);return result;}
# Python
def permute(nums):
result =[]
def backtrack(current, remaining):if not remaining:
result.append(current[:])returnfor i inrange(len(remaining)):
current.append(remaining[i])backtrack(current, remaining[:i]+ remaining[i+1:])
current.pop()backtrack([], nums)return result
Note Time O(n! * n), Space O(n). The 'undo choice' step (current.pop()) is the hallmark of backtracking. For permutations with duplicates: sort first, and skip if nums[i] == nums[i-1] and nums[i-1] was not used in this branch. n! grows extremely fast - practical only for n ≤ ~10.
Choose k items from n without regard to order.Backtrack: at each step, choose to include or exclude the current element.Only pick elements with index >= start to avoid duplicates.
Example
// JavaScriptfunctioncombine(n, k){const result =[];functionbacktrack(start, current){if(current.length=== k){
result.push([...current]);return;}for(let i = start; i <= n; i++){
current.push(i);backtrack(i +1, current);
current.pop();}}backtrack(1,[]);return result;}
# Python
def combine(n, k):
result =[]
def backtrack(start, current):iflen(current)== k:
result.append(current[:])returnfor i inrange(start, n +1):
current.append(i)backtrack(i +1, current)
current.pop()backtrack(1,[])return result
Note Time O(C(n,k) * k), Space O(k) for recursion depth. The key difference from permutations: use a 'start' parameter to only pick forward, preventing duplicate combinations. Optimization: prune when remaining elements < needed elements (n - i + 1 < k - len(current)).
combinationschoose k from nbacktracking combinationsC(n,k)
Subsets (Power Set)
Syntax
Generate all 2^n subsets of a set.Backtrack:for each element, choose to include it or not.Alternative: iterative bit manipulation.
Example
// JavaScriptfunctionsubsets(nums){const result =[];functionbacktrack(start, current){
result.push([...current]);// every state is a valid subsetfor(let i = start; i < nums.length; i++){
current.push(nums[i]);backtrack(i +1, current);
current.pop();}}backtrack(0,[]);return result;}// Iterative approachfunctionsubsetsIterative(nums){const result =[[]];for(const num of nums){const newSubsets = result.map(sub =>[...sub, num]);
result.push(...newSubsets);}return result;}
# Python
def subsets(nums):
result =[]
def backtrack(start, current):
result.append(current[:])for i inrange(start,len(nums)):
current.append(nums[i])backtrack(i +1, current)
current.pop()backtrack(0,[])return result
Note Time O(2^n * n), Space O(n) recursion depth. Unlike combinations, we add to result at every recursive call (not just when a condition is met). For subsets with duplicates: sort the input, then skip nums[i] == nums[i-1] when i > start. The iterative approach is elegant and avoids recursion.
Place n queens on an n×n board so no two attack each other.Backtrack row by row.For each row,try placing a queen in each column.Check conflicts: same column, same diagonal(row-col), same anti-diagonal(row+col).
solve_n_queens(4) → 2 solutions
['.Q..','...Q','Q...','..Q.'] and ['..Q.','Q...','...Q','.Q..']
Note Time O(n!), Space O(n). The diagonal trick: queens on the same diagonal have the same (row-col), same anti-diagonal have same (row+col). Using sets for O(1) conflict checking is more elegant than iterating over placed queens. This is the canonical backtracking problem.
Fill empty cells one at a time.For each empty cell,try digits 1-9.Check row, column, and 3x3 box constraints.Backtrackif no valid digit fits.
Example
// JavaScriptfunctionsolveSudoku(board){functionisValid(board, row, col, num){const ch =String(num);for(let i =0; i <9; i++){if(board[row][i]=== ch)returnfalse;// rowif(board[i][col]=== ch)returnfalse;// columnconst boxR =3*Math.floor(row /3)+Math.floor(i /3);const boxC =3*Math.floor(col /3)+(i %3);if(board[boxR][boxC]=== ch)returnfalse;// box}returntrue;}functionsolve(board){for(let r =0; r <9; r++){for(let c =0; c <9; c++){if(board[r][c]==='.'){for(let num =1; num <=9; num++){if(isValid(board, r, c, num)){
board[r][c]=String(num);if(solve(board))returntrue;
board[r][c]='.';}}returnfalse;// no valid digit → backtrack}}}returntrue;// all cells filled}solve(board);}
# Python
def solve_sudoku(board):
def is_valid(row, col, num):
ch =str(num)for i inrange(9):if board[row][i]== ch:returnFalseif board[i][col]== ch:returnFalse
br =3*(row // 3) + i // 3
bc =3*(col // 3) + i % 3if board[br][bc]== ch:returnFalsereturnTrue
def solve():for r inrange(9):for c inrange(9):if board[r][c]=='.':for num inrange(1,10):ifis_valid(r, c, num):
board[r][c]=str(num)ifsolve():returnTrue
board[r][c]='.'returnFalsereturnTruesolve()
Output
Fills in all '.' cells with valid digits 1-9.
Note Time O(9^(empty cells)) worst case, but pruning makes it much faster in practice. Optimization: use sets for each row, column, and box to check validity in O(1) instead of O(9). Finding the cell with fewest candidates first (MRV heuristic) dramatically speeds up solving.
DFSfrom each cell.At each step, check if the current cell matches the next character.Mark cell asvisited(temporarily modify it), recurse in4 directions, then unmark.
Example
// JavaScriptfunctionexist(board, word){const rows = board.length, cols = board[0].length;functiondfs(r, c, idx){if(idx === word.length)returntrue;if(r <0|| r >= rows || c <0|| c >= cols)returnfalse;if(board[r][c]!== word[idx])returnfalse;const saved = board[r][c];
board[r][c]='#';// mark visitedconst found =dfs(r+1,c,idx+1)||dfs(r-1,c,idx+1)||dfs(r,c+1,idx+1)||dfs(r,c-1,idx+1);
board[r][c]= saved;// unmarkreturn found;}for(let r =0; r < rows; r++)for(let c =0; c < cols; c++)if(dfs(r, c,0))returntrue;returnfalse;}
# Python
def exist(board, word):
rows, cols =len(board),len(board[0])
def dfs(r, c, idx):if idx ==len(word):returnTrueif r <0 or r >= rows or c <0 or c >= cols:returnFalseif board[r][c]!= word[idx]:returnFalse
saved = board[r][c]
board[r][c]='#'
found =(dfs(r+1,c,idx+1) or dfs(r-1,c,idx+1)
or dfs(r,c+1,idx+1) or dfs(r,c-1,idx+1))
board[r][c]= saved
return found
for r inrange(rows):for c inrange(cols):ifdfs(r, c,0):returnTruereturnFalse
Note Time O(m * n * 4^L) where L = word length. The temporary cell modification is a clean alternative to a separate visited set. Always restore the cell after backtracking. Optimization: check if the board has all needed characters before starting DFS.