Recursion — base case, recursive case, recursion-depth limits, tail vs non-tail, when to convert to iteration.
Scanned 8/31/2026
Install to Claude Code
npx -y skills add 0bserver07/chimera --skill recursion --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Recursion?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/0bserver07-recursion)More formats (shields.io, HTML) on the badges page.
---
name: algo-recursion
description: Recursion — base case, recursive case, recursion-depth limits, tail vs non-tail, when to convert to iteration.
when-to-use: "Tree/graph traversal, divide-and-conquer (mergesort, quicksort), backtracking, recursive structure naturally — JSON walks, regex compilation."
---
## Recursion — base case first
A recursive function solves a problem by reducing it to a smaller
instance of the same problem. Get the base case right and the
recursive case becomes a one-line transformation.
### Invariants
* **Base case** terminates the recursion. Without it, infinite
recursion → stack overflow.
* **Recursive case** strictly reduces the problem size — by index,
by length, by tree height. If the size doesn't shrink, you have
unbounded recursion.
* The result of the recursive call is *trusted*. Don't second-
guess; assume it's correct and use it.
### Complexity
* Time: depends on the recurrence. `T(n) = T(n/2) + O(1)` →
`O(log n)`. `T(n) = T(n-1) + O(1)` → `O(n)`. `T(n) = 2 T(n-1)`
→ `O(2^n)`. Use the Master Theorem for divide-and-conquer.
* Space: `O(depth)` for the call stack. For a balanced binary
tree of `n` nodes, depth is `O(log n)`. For a degenerate (linked-
list-shaped) tree, depth is `O(n)`.
### Template — Python (tree traversal, in-order)
```python
class Node:
def __init__(self, val: int, left=None, right=None):
self.val, self.left, self.right = val, left, right
def in_order(root: Node | None, out: list[int]) -> None:
if root is None: return # base case
in_order(root.left, out) # recurse left
out.append(root.val) # visit
in_order(root.right, out) # recurse right
```
The `None` check is the base case. The recursive case trusts
that `in_order(root.left, out)` correctly populates `out` for the
left subtree.
### Template — Python (divide-and-conquer, mergesort)
```python
def mergesort(arr: list[int]) -> list[int]:
if len(arr) <= 1: return arr[:] # base case
mid = len(arr) // 2
left = mergesort(arr[:mid])
right = mergesort(arr[mid:])
return _merge(left, right)
def _merge(a: list[int], b: list[int]) -> list[int]:
out: list[int] = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] <= b[j]: out.append(a[i]); i += 1
else: out.append(b[j]); j += 1
out.extend(a[i:]); out.extend(b[j:])
return out
```
The recursive case is trivial because `_merge` correctly merges
two sorted lists — encapsulation lets the recursion stay clean.
### Template — JavaScript (backtracking, n-queens skeleton)
```javascript
function solveNQueens(n) {
const cols = new Set(), d1 = new Set(), d2 = new Set();
const out = [];
const cur = [];
function place(row) {
if (row === n) { out.push([...cur]); return; }
for (let c = 0; c < n; c++) {
if (cols.has(c) || d1.has(row - c) || d2.has(row + c)) continue;
cols.add(c); d1.add(row - c); d2.add(row + c); cur.push(c);
place(row + 1);
cols.delete(c); d1.delete(row - c); d2.delete(row + c); cur.pop();
}
}
place(0);
return out;
}
```
The `place(row + 1)` recursion makes the choice; the cleanup
afterward (`delete`, `pop`) restores state for the next sibling.
That symmetry is the backtracking discipline.
### Common pitfalls
* **Missing base case.** Stack overflow on the first call.
* **Base case that doesn't terminate the recursion.** E.g. `if n
== 0: return f(n)` — call yourself again.
* **Recursing without shrinking.** Off-by-one in the slice / index
causes the recursive call to receive the same problem.
* **Python recursion depth.** Default 1000. Set
`sys.setrecursionlimit(10**6)` early, or rewrite iteratively.
* **Returning vs mutating.** Decide once: either every recursive
call returns the answer, or every recursive call mutates a
shared accumulator. Mixing the two breaks the contract.
* **Forgetting to copy mutable state when recording solutions.**
Backtracking pushes a reference; a later mutation changes the
recorded answer. Push a copy (`[...cur]`).
* **Repeating identical subproblems without memoisation.** That
is the DP smell — add `@cache` or convert to iteration.
### Test corner cases
* `n == 0` and `n == 1`.
* Tree of depth 1 (root only).
* Maximally unbalanced tree (linked-list shape — depth equals n).
* Backtracking with no solution (return empty list, not crash).
* Backtracking where the *first* choice leads to a dead end (must
unwind correctly).
When recursion runs into the depth limit, the conversion path is
usually: stack-of-frames → explicit `[(state, kind)]` list with
`kind` distinguishing pre- and post-order visits. The structure
maps 1:1 to the recursive function.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!