Forward Checking
Backtracking with a lookout: prune bad values before you trip over them.
Interactive demo coming soon β read the theory belowThe idea
Forward Checking is plain backtracking search with an early-warning system. Every time you assign a value to a variable, you immediately delete any values from the still-unassigned neighbouring variables that now conflict with your choice. If any neighbour is left with no legal values at all, you know this path is doomed, so you backtrack right away instead of blundering deeper. It finds failure sooner and saves wasted work.
How it works
- Start with every variable unassigned and each variable holding its full domain (its list of allowed values).
- Pick an unassigned variable and choose a value from its current domain.
- Look at every unassigned variable connected to it by a constraint (its neighbours).
- From each neighbour's domain, remove any value that would clash with the value you just assigned.
- Check the neighbours: if any neighbour's domain is now empty, this assignment cannot work.
- If a domain went empty, undo the assignment (restore the removed values) and try the next value; if none are left, backtrack to the previous variable.
- If all domains still have at least one value, move on and assign the next variable.
- Repeat until every variable is assigned (success) or you run out of options at the very start (no solution).
OPEN, CLOSED & the data structures
A current assignment (which variable has which value so far), and a domain for each variable β the shrinking set of still-legal values. Forward Checking also keeps a record of which values it pruned at each step so it can restore them exactly when it backtracks (the classic recursive/stack structure of backtracking).
Pseudocode
function ForwardChecking(assignment, domains):
if assignment is complete:
return assignment # every variable has a value
var = pick an unassigned variable
for each value in domains[var]:
assign var = value
pruned = {} # remember what we remove
empty = false
for each unassigned neighbour Y of var:
for each v in domains[Y] that conflicts with value:
remove v from domains[Y]
record v in pruned[Y]
if domains[Y] is empty:
empty = true # a neighbour has no options
if not empty:
result = ForwardChecking(assignment, domains)
if result != failure:
return result
restore all values in pruned to their domains # undo
unassign var
return failure # no value worked -> backtrack
At a glance
π When to use it
- Constraint problems like map colouring, Sudoku, N-Queens, or timetabling/scheduling
- When plain backtracking wastes time exploring branches that were doomed from an early bad choice
- When constraints are mostly between pairs of variables, so a neighbour's options are easy to prune
- As a light, cheap upgrade to backtracking before reaching for heavier methods like full constraint propagation (AC-3)
β οΈ Watch out for
- It only checks the direct neighbours of the just-assigned variable β it does NOT propagate further, so some doomed states slip through until later (that is what stronger methods like arc consistency / AC-3 catch).
- You must restore the pruned values exactly when backtracking, or later branches will be missing legal options and you may wrongly report no solution.
- It is still exponential in the worst case β it prunes, it does not magically make the problem easy.
- Forgetting that a value was removed by an earlier assignment (not the current one) leads to restore bugs β track pruning per assignment step.
- Pairs it best with smart heuristics (e.g. assigning the most-constrained variable first); on its own the ordering can still matter a lot.