Iterative-Deepening A* (IDA*)
DFS on a leash: raise the cost limit until the goal appears.
Interactive demo coming soon β read the theory belowThe idea
IDA* is A*'s memory-light cousin. It runs plain depth-first search, but with a rule: never go past a "budget" measured by f = g + h, where g is the cost so far and h is a guess of the cost still to go. Each round it tries again with the smallest budget that was too tight last time. This keeps A*'s smart, goal-directed guidance while using almost no memory.
How it works
- Set the starting budget (the threshold) to f(start) = h(start), since g is 0 at the start.
- Begin a depth-first search from the start node, tracking g (cost accumulated on the current path).
- At each node, compute f = g + h. If f is greater than the current threshold, do NOT expand it β instead remember this f as a candidate for the next threshold and back up.
- If a node is the goal, stop and return the path β this is your answer.
- Otherwise keep diving depth-first into children, exactly like DFS, so only the current path sits in memory.
- When the whole search finishes without a goal, look at all the f values that exceeded the threshold and take the smallest one.
- Raise the threshold to that smallest exceeding f and run the entire depth-first search again from scratch.
- Repeat until the goal is found; because the threshold climbs in small steps, the first goal found is the cheapest.
OPEN, CLOSED & the data structures
The recursion stack itself (holding only the current root-to-node path) plus a single number: the current f-threshold. During each round it also tracks the minimum f value seen that exceeded the threshold, which becomes the next round's threshold. Unlike A*, there is no big OPEN priority queue and no CLOSED set of all visited nodes.
Pseudocode
function IDA_STAR(start):
threshold = h(start) # f = g + h, and g(start) = 0
loop:
next_threshold = INFINITY
found = DFS(start, g=0, threshold) # returns FOUND or the min exceeding f
if found is a goal path: return found
if next_threshold == INFINITY: return FAILURE # no nodes left to try
threshold = next_threshold # raise to smallest f that overshot
function DFS(node, g, threshold):
f = g + h(node)
if f > threshold:
next_threshold = min(next_threshold, f) # remember for next round
return NOT_FOUND
if node is goal: return path-to(node)
for each child of node:
result = DFS(child, g + cost(node, child), threshold)
if result is a goal path: return result
return NOT_FOUND
At a glance
π When to use it
- You want A*-quality optimal answers but A* uses too much memory.
- Puzzles with huge state spaces and admissible heuristics, e.g. the 15-puzzle or Rubik's cube.
- Edge costs vary (not all steps equal), so a plain depth counter won't do β you need f = g + h.
- Memory is the bottleneck, and re-doing some work each round is an acceptable trade.
β οΈ Watch out for
- It re-expands the same shallow nodes every round β wasteful when costs are many distinct real numbers (each round may add just one node).
- The threshold must rise to the smallest f that EXCEEDED it, not by a fixed step; guessing a step size breaks optimality.
- With a non-admissible (overestimating) h, you lose the optimality guarantee.
- Basic IDA* keeps only the current path, so it can revisit the same state via different paths and won't detect cycles unless you add a path check.
- It shines with a few discrete cost levels; on graphs with wildly varying costs, prefer A* or a variant like RBFS.