// Preorder traversalfunction preorder(root) { if (!root) return; console.log(root.val); // Process root preorder(root.left); // Recurse left preorder(root.right); // Recurse right}// Inorder traversalfunction inorder(root) { if (!root) return; inorder(root.left); console.log(root.val); inorder(root.right);}// Postorder traversalfunction postorder(root) { if (!root) return; postorder(root.left); postorder(root.right); console.log(root.val);}// Time: O(n), Space: O(h) recursion stack
Example: N-ary tree recursion
function traverseNary(root) { if (!root) return; // Process current node console.log(root.val); // Recurse on all children for (const child of root.children) { traverseNary(child); }}// Count nodes in N-ary treefunction countNodes(root) { if (!root) return 0; let count = 1; // Current node for (const child of root.children) { count += countNodes(child); } return count;}// Time: O(n), Space: O(h)
Example: Tree recursion with return value
// Find maximum value in binary treefunction findMax(root) { if (!root) return -Infinity; const leftMax = findMax(root.left); const rightMax = findMax(root.right); return Math.max(root.val, leftMax, rightMax);}// Check if tree is symmetricfunction isSymmetric(root) { function isMirror(left, right) { if (!left && !right) return true; if (!left || !right) return false; return left.val === right.val && isMirror(left.left, right.right) && isMirror(left.right, right.left); } return !root || isMirror(root.left, root.right);}// Path sum existsfunction hasPathSum(root, targetSum) { if (!root) return false; // Leaf node if (!root.left && !root.right) { return root.val === targetSum; } return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);}
function factorialTail(n, acc = 1) { if (n <= 1) return acc; // Tail recursive // No pending operations after recursion return factorialTail(n - 1, n * acc);}// With TCO optimization:// factorialTail(5, 1)// factorialTail(4, 5) // reuse frame// factorialTail(3, 20) // reuse frame// factorialTail(2, 60) // reuse frame// factorialTail(1, 120) // reuse frame// return 120// Space: O(1) with TCO// Without TCO: still O(n) in JavaScript
Example: Converting to iterative (guaranteed O(1) space)
// Tail recursive Fibonaccifunction fibTail(n, a = 0, b = 1) { if (n === 0) return a; if (n === 1) return b; return fibTail(n - 1, b, a + b);}// Iterative version (manual tail call elimination)function fibIterative(n) { if (n === 0) return 0; if (n === 1) return 1; let a = 0, b = 1; for (let i = 2; i <= n; i++) { [a, b] = [b, a + b]; } return b;}// Both O(n) time, iterative guaranteed O(1) space// fibTail depends on TCO support// Sum of array - tail recursivefunction sumTail(arr, index = 0, acc = 0) { if (index === arr.length) return acc; return sumTail(arr, index + 1, acc + arr[index]);}// Iterative equivalentfunction sumIterative(arr) { let sum = 0; for (const num of arr) { sum += num; } return sum;}
function fibMemo(n, memo = {}) { // Check cache if (n in memo) return memo[n]; // Base cases if (n <= 1) return n; // Compute and cache memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo); return memo[n];}// Alternatively with arrayfunction fibMemoArray(n) { const memo = Array(n + 1).fill(-1); function helper(n) { if (memo[n] !== -1) return memo[n]; if (n <= 1) return n; memo[n] = helper(n - 1) + helper(n - 2); return memo[n]; } return helper(n);}// Time: O(n), Space: O(n)// Each subproblem computed once!
Example: Climbing stairs memoized
function climbStairs(n) { const memo = {}; function climb(n) { if (n in memo) return memo[n]; if (n <= 2) return n; memo[n] = climb(n - 1) + climb(n - 2); return memo[n]; } return climb(n);}// Time: O(n), Space: O(n)// Without memoization: O(2^n)// Can take 1 or 2 steps at a time// How many ways to reach step n?// climb(n) = climb(n-1) + climb(n-2)
Mutual Recursion: Functions call each other, useful for state machines and parsers
Note:Memoization is critical for recursive problems with overlapping subproblems. Fibonacci naive: O(2^n), with memoization: O(n). Tail recursion optimization converts recursion to iteration internally, achieving O(1) space, but JavaScript engines may not guarantee TCO. When in doubt, manually convert to iteration.
Warning: Deep recursion can cause stack overflow. JavaScript typically allows ~10,000-50,000 frames. For very deep recursion, use iteration or trampolining. Memoization requires extra space - be mindful of memory constraints. In mutual recursion, ensure all functions have proper base cases to avoid infinite loops.