Bidirectional Search (BDS)
Dig from both ends of the tunnel and meet in the middle.
The idea
Bidirectional Search runs two searches at the same time: one going forward from the start, and one going backward from the goal. Each search grows its own frontier, and the moment the two frontiers touch, you stitch their paths together into one full route. Because each side only has to travel half the distance, you explore far fewer options than searching all the way from one end.
How it works
- Put the START node in the forward OPEN list, and the GOAL node in the backward OPEN list. Both CLOSED lists start empty.
- Expand one node from the forward side: take it off forward OPEN, add it to forward CLOSED, and put its neighbors on forward OPEN.
- Expand one node from the backward side the same way, but follow moves in reverse (which is why moves must be reversible).
- After each expansion, check whether the node you just reached already appears in the other side's CLOSED (or OPEN). If it does, the two searches have met.
- When they meet at a shared node, stop: build the answer by joining the forward path (start to meeting node) with the backward path (meeting node to goal).
- If they haven't met, alternate sides β keep expanding forward, then backward, one layer at a time using BFS on each side.
- Keep going until the frontiers meet (success) or both OPEN lists empty out (no path exists).
See it step by step
Watch Bidirectional Search search a real problem β flying from Sydney (SYD) to London (LHR). Click Next to advance one expansion and watch OPEN, the traversal, the path and the cost update live.

OPEN, CLOSED & the data structures
Each direction has its own OPEN (the frontier of nodes waiting to be expanded) and its own CLOSED (nodes already expanded). Since we use BFS on both sides, each OPEN is a FIFO queue, so nodes come out in the order they were discovered β nearest first. The two CLOSED lists do double duty: they record where each search has been, and they are what you check against to detect the moment the searches overlap.
Pseudocode
function BidirectionalSearch(start, goal):
OPEN_f = queue([start]); CLOSED_f = {} # forward, from start
OPEN_b = queue([goal]); CLOSED_b = {} # backward, from goal
while OPEN_f not empty and OPEN_b not empty:
# --- expand one node from the forward side ---
n = OPEN_f.dequeue(); CLOSED_f.add(n)
if n in CLOSED_b: return join(n) # frontiers met
for m in neighbors(n):
if m not in CLOSED_f: OPEN_f.enqueue(m)
# --- expand one node from the backward side ---
n = OPEN_b.dequeue(); CLOSED_b.add(n)
if n in CLOSED_f: return join(n) # frontiers met
for m in reverse_neighbors(n):
if m not in CLOSED_b: OPEN_b.enqueue(m)
return failure # no path exists
At a glance
π When to use it
- You know exactly what the goal is (not just a test for it), so you can start searching backward from it
- Moves are reversible β you can work backward from the goal the same way you work forward
- The graph is deep and branchy, where one-directional BFS would blow up as b^d
- You want BFS's shortest-path guarantee but can't afford its memory or time cost
β οΈ Watch out for
- You must have an explicit goal state to start the backward search β if the goal is only described by a test ("any winning board"), you can't search backward from it.
- Moves must be reversible; if you can't undo a move, the backward search has nothing to follow.
- Detecting the meeting point is the tricky part β check each newly expanded node against the other side's CLOSED, and don't stop at the very first touch without confirming it gives the shortest combined path.
- Keeping two OPEN and two CLOSED lists in sync is more bookkeeping than a plain one-directional search β it's easy to check the wrong side's list.
Now see it for yourself
Open Bidirectional Search in the visualizer β step through it, pause, and watch OPEN & CLOSED change.