Combinatorial Exploration
See every possibility emerge
Small systems can be exhaustively known. This piece enumerates all solutions to combinatorial problems — watching the complete solution space reveal itself, one possibility at a time. The pattern emerges not from any single solution, but from seeing how they all relate.
The Generator Function
Combinatorial enumeration uses JavaScript generator functions to yield solutions one at a time. The generator pattern lets us pause execution after each discovery, making the exploration visible as it unfolds rather than computing everything upfront.
function* enumerateSubsets(idx, current) {
if (idx === set.length) {
yield { solution: [...current], path: [...current] };
return;
}
yield* enumerateSubsets(idx + 1, current);
yield* enumerateSubsets(idx + 1, [...current, set[idx]]);
}
Branch and Bound
For problems like N-Queens, we use constraint pruning to skip branches that can't possibly work. When a queen threatens any already-placed queen, we don't explore that branch further. This is the core insight of backtracking: early rejection prevents wasted work.
function isSafe(board, row, col) {
for (let r = 0; r < row; r++) {
const c = board[r];
if (c === col || Math.abs(c - col) === row - r) {
return false;
}
}
return true;
}
State Space Trees
Every combinatorial problem has an implicit tree structure. For subsets, each level represents a choice: include this element or don't. For permutations, each level represents a position in the ordering. The enumeration walks this tree, yielding complete paths as solutions.
// Subsets: binary tree of depth n
// Permutations: n-ary tree where siblings represent swapped elements
// The recursion depth equals the solution size
yield* solve(currentDepth + 1, extendedState);
Visualizing Enumeration
The visualization shows solutions appearing in discovery order. This order isn't arbitrary — it reveals the structure of the search space. For subsets, solutions emerge in a pattern: first the empty set, then singletons, then pairs, building up to the full set. Each pattern tells you something about the underlying generation algorithm.
solutions.push(result.value);
updateStats();
draw(); // Render after each step to show emergence
What This Reveals
Exhaustive enumeration is a form of complete knowledge — for small systems, we can see every possibility laid out. But the gap between "all possibilities" and "interesting possibilities" is where algorithms live. Heuristics, constraints, and pruning transform an explosion of options into tractable search. This piece shows the raw unpruned space, which makes the need for efficient algorithms obvious: without branch-and-bound or other pruning, the exponential explosion makes even moderate inputs impossible to explore.
The three problems here span a range: subsets grow as 2^n, permutations as n!, and N-Queens can be pruned dramatically by constraint propagation. Watching enumeration in real time makes these growth rates tangible — you feel the explosion in a way that O-notation alone doesn't convey.