rajeshrmahapatra.com Research

AI Research Library

Search as structured exploration

Rajesh Ranjan Mahapatra

Breadth-first, depth-first and uniform-cost search are one exploration engine governed by different frontier contracts, and those contracts determine which result is merely found, which is cheapest, and which may never be reached.

© 2026 Rajesh Ranjan Mahapatra. 34 min read

Search as structured explorationAll AI Research Library

Search as structured exploration

Breadth-first, depth-first and uniform-cost search are one exploration engine governed by different frontier contracts, and those contracts determine which result is merely found, which is cheapest, and which may never be reached.

TLDR

  1. Search is disciplined postponement. A frontier stores paths that are possible but not yet expanded, while an ordering rule decides which possibility earns the next unit of computation.
  2. Breadth-first, depth-first and uniform-cost search share one loop. They differ chiefly in whether eligibility follows path depth, recency or accumulated cost.
  3. The container name is not the complete algorithm. State identity, duplicate handling, replacement, tie-breaking and goal-test timing carry part of every correctness guarantee.
  4. Uniform-cost optimality has a hard boundary. It depends on nonnegative additive costs and on accepting a goal only when its path is the cheapest pending possibility.
  5. Choose search by writing a frontier contract: what a state is, what a path costs, which record dominates, when a goal counts, and which resource limit may stop exploration.

The route that changed when nothing moved

A maintenance robot must reach a coolant isolator inside a warehouse. The map, start and goal remain fixed. Only the rule for choosing the next unfinished route changes.

One route reaches the isolator through two powered doors. It has only two actions, but each door takes seven minutes to cycle. A second route crosses four open aisles at one minute each. A third branch begins with an inviting open aisle, then runs deep into an inspection gallery before returning to the useful part of the map.

A breadth-first search returns the two-door route because two actions are fewer than four. A uniform-cost search returns the four-aisle route because four minutes are cheaper than fourteen. A depth-first search follows whichever successor was placed last on its stack, so it may inspect the gallery before either useful route. The world did not change. The meaning of “next” changed.

This is easy to describe as three data structures: queue, priority queue and stack. That account is memorable, but incomplete. Put the same records into those containers while testing the goal at the wrong time, collapsing distinct states, or refusing to replace an expensive pending path with a cheaper one. The familiar guarantee disappears even though the container still has the expected name.

Core answer

A search algorithm is a frontier contract

The frontier rule allocates computation among unfinished paths. Correctness then depends on how path records correspond to world states, which record is allowed to supersede another, and when a result is certified rather than merely noticed.

Three expanding wavefronts over the same warehouse state space Three panels show breadth-first search spreading in even depth rings, depth-first search extending a narrow tendril, and uniform-cost search spreading through low-cost cells while avoiding costly powered doors. Same start, goal and successor graph. Different eligibility rules. Breadth-first eligible by shallowest depth powered doors START GOAL Result: 2 transitions through doors Cost is invisible to the ordering key. Depth-first eligible by deepest, newest path START GOAL Result: depends on successor order A narrow branch gets commitment first. Uniform-cost eligible by lowest accumulated cost START GOAL Result: 4 low-cost aisle actions The contour bends around expensive terrain. The frontier is a moving boundary between paths already expanded and paths still entitled to compete.
Figure 1. Three wavefronts, one state space.Breadth-first search expands equal-depth rings, depth-first search forms a tendril, and uniform-cost search follows contours of accumulated cost. The geometric shape is a consequence of the eligibility key, not a different world model.Illustrative running case. The wavefront idea follows classical maze and path-connection search; all values and geometry here are synthetic.

The frontier is the algorithm’s moving present

A discrete search problem begins with less machinery than its names suggest. There is a set of states, an initial state, a way to generate legal successors, a goal predicate and, when routes differ in value, a rule for extending path cost. The complete transition graph need not exist in memory. Search reveals a reachable subgraph only where expansion demands it.

LaValle’s general forward-search template divides states into unvisited, alive and dead. Alive states have been encountered but may still contribute new successors. They inhabit a priority queue whose sorting function distinguishes one method from another.6 His later unified view makes the common structure explicit: initialise a partial search graph, select a vertex, apply an action and conditionally insert the resulting state and edge.7

The word frontier is useful because a queue is only one implementation. Conceptually, the frontier is the set of path records that have been generated but not yet expanded. It separates an explored interior from an ungenerated exterior. Every record is a claim: “This path could still lead to a result that matters.” Selection grants one such claim the next unit of computation.

The frontier as a membrane between explored and unexplored state space An irregular explored region contains expanded states. A ring of pending path records forms the frontier. Outside it lies ungenerated state space. Two frontier records point to the same world state, showing why path records and states must be distinguished. The frontier stores unfinished path claims, not merely locations. EXPLORED INTERIOR records already removed and expanded UNGENERATED no record exists yet FRONTIER pending path records state = aisle-4depth = 3g = 3 state = door-Bdepth = 2g = 14 state = aisle-4depth = 5g = 8 state = gallerydepth = 7g = 7 same world state, two path records Duplicate handling asks which path claim may still beat the representative already recorded for a state.
Figure 2. The frontier membrane.A state describes a possible world configuration. A node describes a path to that state. Conflating them hides the need to compare competing path records and is a common source of incorrect pruning.Practitioner synthesis grounded in general graph-search templates.6, 7, 8

A node is a path record, not a state

Suppose two routes both reach aisle 4. The world state may be identical, but the path records can differ in parent, action history, depth and accumulated cost. Breadth-first search can usually discard a later route to the same state because the first discovery is already shallowest when every action has equal cost. Uniform-cost search cannot apply that rule blindly. A later route may be cheaper and must replace or supersede the earlier record.

A useful minimal record is:

node = (state, parent, action, depth, g)

Here g is the accumulated path cost. Parent pointers recover the chosen route after the goal is accepted. The state key controls duplicate detection. The remaining fields support ordering, comparison and explanation.

The problem contract precedes the frontier contract

A useful formal boundary is a problem tuple P = (S, s₀, A, T, G, c). S is the state space; s₀ the initial state; A(s) the actions legal in state s; T(s,a) the successor relation; G(s) the goal predicate; and c(s,a,s′) the incremental path cost. Search does not repair ambiguity in these elements. It operationalises them.

This distinction matters because an algorithm can be internally correct and externally useless. If G recognises “a document exists” when the real requirement is “an approved document exists”, the search may optimally reach the wrong terminal condition. If T omits a legal recovery action, no frontier discipline can discover it. If c encodes elapsed time while the decision is governed by irreversible risk, UCS may faithfully optimise the wrong quantity.

The problem contract therefore answers what counts as a possible world and a successful route. The frontier contract answers how pending routes compete for computation. The first is semantic; the second is procedural. Combining them too early encourages a familiar mistake: treating the path returned by a correct algorithm as evidence that the problem was correctly specified.

Tree search and graph search are different evidence policies

Tree search keeps every generated path as a distinct possibility. It is appropriate when the underlying state space is genuinely a tree or when different histories must remain distinct. On a graph, however, cycles and converging paths can generate the same state repeatedly. A tree-search DFS may follow a cycle indefinitely. A tree-search UCS may create an unbounded number of increasingly redundant records.

Graph search adds a reached or explored structure. That structure is not merely a speed cache. It asserts that a stored representative is sufficient for all future reasoning about a state. AIMA’s reference code makes this contrast visible: its tree-search version drops explored-state and frontier-replacement bookkeeping and warns that cyclic graphs may not terminate, while graph search retains those controls.8

Seen this way, duplicate detection is a form of proof compression. Instead of preserving every history, the algorithm keeps one record or a small nondominated set and claims that discarded histories cannot improve the result. The memory saving is earned only when that claim is true.

Recent learned planning makes the risk concrete. Distincter uses a graph representation to guide search and identify symmetries, then applies action or state pruning inside Fast Downward. It improved coverage on the 2023 planning competition’s learning track, but the boundary is as important as the gain: ordinary graph networks can fail to distinguish non-isomorphic states, state pruning can threaten completeness, and the extra symmetry test can cost more than it saves. Learning can propose equivalence. The search engine still needs a defensible rule for when equivalence is safe to act on.

Thought experiment 1

Change one doorway, preserve the map

Keep every state and edge fixed. Increase the cycle time of one powered door from one minute to one hundred. Breadth-first search produces exactly the same expansion order because depth did not change. Uniform-cost search bends away from the door because accumulated cost changed. The experiment isolates the mechanism: BFS is invariant to edge-cost changes that preserve topology; UCS is designed not to be.

The loop beneath the names

The shared loop is compact. Put the initial path record on the frontier. Remove the eligible record. If it certifies a goal, return its parent chain. Otherwise, generate successors, create child records, compare each child with the current representative for its state, and keep only records that remain entitled to compete. Repeat until a result is certified, the frontier empties, or a resource boundary stops the run.

Search is not “trying everything”. It is a disciplined schedule for postponing most possibilities while preserving enough of them to support a chosen guarantee.

The strongest competing explanation

The simplest rival thesis says that the algorithm is exhausted by its container: queue means BFS, stack means DFS, heap means UCS. That explanation is attractive because it predicts the expansion order in clean finite trees. It is also the right teaching compression for a first implementation.

It becomes insufficient when a guarantee depends on information outside the container. Hold the min-heap fixed and test a UCS goal on generation rather than removal: optimality can fail. Hold the FIFO queue fixed and key states only by location rather than location plus carried key: completeness can fail. Hold the stack fixed and remove cycle detection: termination can fail. These interventions change the result without changing the named container. The broader frontier contract earns its place by explaining those controlled failures.

The argument in one passChart · flow
flowchart TD
  A["The route that changed when<br/>nothing moved"] --> B["The frontier is the<br/>algorithm’s moving present"]
  B --> C["One engine, three eligibility<br/>disciplines"]
  C --> D["The guarantee lives in four<br/>places at once"]
  D -. "re-test" .-> A
  classDef first fill:#0a1424,stroke:#D95545,stroke-width:2px,color:#e6f3ff
  classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff
  class A first
  class B,C,D step
A structural route through Search as structured exploration, from The route that changed when nothing moved to References and source ledger. Reading use: follow the relation, then test it against the surrounding evidence and limits.

One engine, three eligibility disciplines

Let F be the frontier and let K(n) be the eligibility key for node n. A generic best-first view removes a node with the smallest key. Breadth-first and uniform-cost search fit directly. Depth-first search can be expressed by favouring greater depth and newer insertion order, or implemented more naturally as a LIFO stack.

next(F) = arg min over n in F of K(n)

A tie-breaker matters because keys are often equal. Python’s heapq documentation recommends a monotonically increasing entry count to preserve insertion order and avoid comparing non-comparable task objects when priorities tie.11 This is an implementation detail with visible behavioural consequences. Change the tie-break and DFS may enter a different branch; UCS may return a different member of several equally cheap solutions.

One frontier engine with three eligibility keys A central frontier engine sends the same four path records through three ordering lenses. Breadth-first orders by depth, depth-first by negative depth and recency, and uniform-cost by accumulated cost. Change the key, and the same pending records form a different search. FRONTIER ENGINE pop eligible record, expand, compare, push A(d=2,g=14)B(d=4,g=4)C(d=3,g=2)D(d=4,g=7) Breadth-first K = (depth, serial) 1. A depth 2 2. C depth 3 3. B depth 4 4. D depth 4 Preserves nondecreasing transition count. Depth-first K = (-depth, -serial) 1. D depth 4, newest 2. B depth 4 3. C depth 3 4. A depth 2 Commits to one branch before its siblings. Uniform-cost K = (g, serial) 1. C cost 2 2. B cost 4 3. D cost 7 4. A cost 14 Preserves nondecreasing settled path cost. The key chooses the next claim. Dominance and goal timing decide whether that claim can be certified.
Figure 3. One engine, three eligibility keys.All three methods can be understood as frontier ordering. This unification is exact only when the associated duplicate and replacement policy is preserved as well.Illustrative synthesis based on LaValle’s general template and the AIMA best-first implementation.6, 8

Breadth-first search: pay by transition count

Breadth-first search expands shallow nodes before deeper ones. Edward Moore’s maze work and C. Y. Lee’s path-connection algorithm are early landmarks in wavefront search for shortest unweighted paths and wiring routes.1, 2 In a graph with unit action costs, depth equals path cost. A FIFO queue therefore implements a special case of uniform-cost ordering without a heap.

The guarantee is precise: when every action has the same cost and the state key is sound, the first discovered path to a state is shallowest. With finite branching and a reachable shallow goal, breadth-first search is complete. The price is the wavefront. A branching factor of three produces 3, 9, 27 and 81 nodes at successive depths. Most memory is spent on paths that have been generated but not yet expanded.

Why the shallowest guarantee follows

The proof is an ordering invariant. Initially, the frontier contains only depth zero. Assume every record removed so far has depth at most k, and the queue is processing depth k. Every child it appends has depth k+1 and is placed behind all already-pending records of depth k. Therefore no depth k+1 record can be removed while a depth k record remains. By induction, removal depth never decreases.

If all actions cost the same positive amount, lower depth is lower total cost. If costs differ, the induction still proves nondecreasing depth, but depth no longer proves lower cost. This is why saying that BFS finds “the shortest path” is incomplete unless shortest means fewest edges or every edge has equal cost.

Worked example 1 · Minimal graph

Two steps can cost more than three

Let the graph contain S → A → G with edge costs 6 and 6, and S → B → C → G with costs 1, 1 and 1. Breadth-first search returns S-A-G because depth two precedes depth three. Its answer is correct for “fewest actions” and wrong for “least total cost”. The failure is not a bad implementation. It is a mismatch between the objective and the frontier key.

Depth-first search: pay with commitment

Depth-first search expands a child before the current node’s unexpanded siblings. It can be implemented recursively or with a stack. NIST’s definition captures the structural rule: outgoing edges of a vertex are considered before siblings, so the extremes are searched first.9 Tarjan showed the deeper algorithmic value of this traversal order by deriving linear graph algorithms for strongly connected and biconnected components.4

For path finding, DFS trades breadth of coverage for a small active working set. It remembers a branch, enough siblings to backtrack, and a visited structure when cycles matter. It can reach a deep solution without storing every shallower alternative. Yet successor order becomes destiny. In an infinite branch, an unrestricted DFS may never return to a sibling that contains a shallow goal. On cyclic graphs, tree-search DFS can loop forever.

“DFS uses linear memory” also needs a boundary. A recursive traversal of a tree stores the active path and local iteration state, which scales with depth. An explicit stack may additionally hold unexpanded siblings along that path, often described as proportional to branching factor times maximum depth. A graph-search implementation also stores visited states, which can approach the reachable graph size. DFS reduces the frontier wavefront; it does not make graph memory disappear.

Its order sensitivity can be productive. Backtracking solvers deliberately order variables and values so that contradictions appear early. Tarjan’s algorithms exploit discovery and finishing structure, not path optimality.4 The right conclusion is not that DFS is inferior, but that depth-first ordering is evidence about structural exploration, not evidence of a cheapest route.

Iterative deepening repairs part of this boundary by running depth-limited DFS with increasing limits. Korf established its strong time and space properties for exponential tree search.5 The repeated upper levels look wasteful, but the deepest level dominates when branching is exponential. Iterative deepening is therefore not a compromise invented by averaging BFS and DFS. It is a deliberate schedule that repeatedly changes which depth is eligible.

Uniform-cost search: pay by the path-cost algebra

Uniform-cost search removes the pending node with the smallest accumulated cost g. In finite graphs with nonnegative edge weights, this is the goal-directed form of Dijkstra’s label-setting method. Dijkstra’s original note formalised shortest-path computation, and NIST states the key precondition directly: all weights must be nonnegative.3, 10

The algorithm keeps a best known cost for each state. If a newly generated path is cheaper, it becomes the representative. A practical heap implementation can push the new record and leave the older one in place, then discard the stale record when it is popped. Python’s heap documentation describes this lazy-removal pattern for changed priorities.11

Why the cheapest removed record can be settled

Let n be the valid frontier record with minimum accumulated cost. Suppose there were a cheaper undiscovered route to the same state. Follow that hypothetical route from the start until it first crosses from an expanded state to an unexpanded state. The prefix at that crossing must already have generated a frontier record. With nonnegative edge costs, that prefix cannot cost more than the full hypothetical route, so it would be cheaper than n. This contradicts the choice of n as the cheapest pending record.

The argument explains three implementation rules at once. The frontier must be ordered by accumulated cost. Improved records must replace or supersede worse ones. A state is settled on removal of the cheapest valid record, not on first generation. Dijkstra’s method “flows only once” through settled finite states because nonnegative extension preserves this monotone boundary.7

For a single target, UCS may stop when a valid goal record is removed. For all-target shortest paths, it continues until the frontier is empty or all required states are settled. These are different stopping contracts over the same cost-ordered mechanism.

Uniform-cost search does not make cost objective. It makes the declared cost additive and operational. Minutes, money and risk points are not interchangeable merely because each is numeric. If the decision is lexicographic, constrained or path-dependent, the state or cost algebra must represent that structure before the frontier can order it honestly.

Additivity can sometimes be recovered by expanding the state. A route with a maximum of two privileged actions can include “privileged actions used” in the state, making the constraint explicit. A turn penalty can be represented by including the previous direction. This increases the state space because history that once looked irrelevant now changes future cost. The modelling trade is exact: either preserve relevant history in the state or accept that two apparently identical arrivals are not safely mergeable.

Asymptotic labels should also be read through assumptions. BFS is commonly summarised as exponential in shallowest solution depth for a tree with branching factor b. DFS can be exponential in maximum explored depth while keeping a much smaller frontier. UCS expands every state whose best cost lies below the optimal solution cost, plus some ties; its work therefore depends on the density of low-cost states, not merely on edge count. A zero-cost plateau can be more damaging than a visually deep graph.

Negative control

When the container story is enough

Consider a finite tree with no repeated states, unit edge costs and a goal predicate that is checked only when a node is removed. There is no replacement problem, no state aliasing and no cost disagreement. A FIFO queue, LIFO stack or min-heap explanation predicts the methods correctly. This control prevents the broader frontier contract from becoming ceremony: extra machinery is justified by graph structure and guarantees, not by a desire to make simple search sound complicated.

DisciplineEligibility keyWhat the first result meansCharacteristic strengthFailure boundary
Breadth-firstLowest depth, then insertion orderFewest transitions when action costs are equalComplete for finite branching with a reachable finite-depth goalFrontier memory grows exponentially with solution depth
Depth-firstDeepest, newest pending pathA result on the chosen branch orderSmall active frontier and natural backtrackingNot optimal; may miss a sibling indefinitely in infinite depth
Uniform-costLowest accumulated path costLeast declared additive cost under its assumptionsHandles unequal nonnegative action costsNegative costs, unsound state identity or premature goal acceptance break the guarantee

The guarantee lives in four places at once

“Use a queue for BFS and a heap for UCS” identifies an important mechanism. It does not yet prove that the returned path has the intended property. Four additional choices determine whether the algorithm’s local ordering produces a global guarantee: state identity, dominance, goal-test timing and cost assumptions.

State identity decides which futures are interchangeable

A duplicate test assumes that two records with the same state key have the same relevant future. This is a stronger claim than visual similarity. A robot at aisle 4 while carrying a service key is not interchangeable with the robot at aisle 4 without it, because available actions differ. A recovery workflow at “database restarted” before verification is not the same state as “database restarted and readback passed”.

Thought experiment 2

The key that vanished from the state

Run the warehouse search twice. In both runs the robot reaches the same coordinate. In one, it previously collected a door key; in the other, it did not. If the state key stores only coordinates, the second arrival can mark the coordinate visited and cause the key-bearing route to be discarded as a duplicate. Varying only the state representation turns a solvable problem into apparent failure. Duplicate pruning is valid only under a future-equivalence claim.

State aliasing caused by omitting key possession Two paths reach coordinate A4. One state includes key possession and can open the goal door; the other does not. An unsafe state key based only on coordinate merges them and prunes the solvable branch. A duplicate key is a claim about identical reachable futures. START collect keyaction skip key lockeraction state = (A4, key=true)successor: open goal doorfuture includes GOAL state = (A4, key=false)successor: door remains lockedfuture excludes GOAL GOAL DOORopens with key unsafe key: state_key = coordinateboth records collapse to A4
Figure 4. The state-aliasing trap.If omitted information changes available successors or future cost, the two configurations are different search states. A visited set built on coordinates alone can remove the only solvable route.Synthetic counterexample. It demonstrates a representation failure, not a measured deployment result.

Dominance decides which path records remain alive

A dominance rule answers: when two records reach the same state, which can be discarded without losing the target guarantee? For BFS with unit costs, lower depth dominates higher depth. For UCS, lower accumulated cost dominates higher cost. For ordinary reachability DFS, first seen may be sufficient on a static graph, but it does not confer path optimality.

Dominance can be partial. Suppose paths track both money and elapsed time, and neither can be collapsed into a trusted scalar. A path costing £4 and taking ten minutes does not dominate one costing £6 and taking four minutes. The frontier may need a Pareto set, a constraint or a lexicographic rule. A scalar priority is a modelling decision before it is an implementation convenience.

Replacement policy and heap policy are separate. A min-heap answers which pending record has the smallest key. It does not know whether two records represent the same state, whether one dominates the other, or whether a popped record has become stale. Those judgements live in the reached map. This is why “use a priority queue” is necessary but not sufficient advice for UCS.

Tie handling deserves the same explicitness. Equal-cost routes can differ in length, reversibility or explanatory quality. If those distinctions matter, they belong in a secondary key or in the objective itself. If they do not, a deterministic insertion counter is still useful for repeatability. It does not improve optimality, but it makes traces, tests and incident reconstruction stable.

Goal timing distinguishes notice from certification

A goal can be tested when it is generated or when it is removed from the frontier. In BFS with uniform action costs, generation-time testing can safely return the shallowest goal because all parents at the previous depth are being processed before deeper nodes. In UCS, generation only proves that one route to the goal exists. A cheaper unfinished route may still be pending.

Premature goal testing breaks uniform-cost search A graph has a direct edge from start to goal costing nine and a two-edge route via A costing two. A timeline shows that testing the goal when generated returns nine, while testing when popped allows the cheaper path to replace it and returns two. Finding a goal path is not yet proving that no cheaper pending path exists. S A GOAL cost 9 cost 1 cost 1 direct path: 9 · path through A: 2 Uniform-cost timeline pop S at g=0generate A(1), G(9) unsafe stopgeneration test returns G(9) safe continuationpop A(1), generate G(2) certified stoppop cheapest pending G(2) Decision rule For generic UCS, accept a goal when it is removed as the cheapest valid frontier record, not merely when a successor generator first notices it.
Figure 5. Goal generation is not goal certification.The direct route reaches the goal first but costs nine. The pending route through A later produces cost two. Testing on removal preserves the nondecreasing-cost argument used by UCS.Synthetic counterexample. The AIMA best-first reference implementation tests the goal after popping the lowest-key record.8

Cost assumptions make the settled frontier monotone

Dijkstra’s logic relies on nonnegative edges. Once the cheapest pending record is removed, extending any other pending path cannot produce a cheaper route back to that settled state. A negative edge destroys that monotonicity. The future can undercut a record that looked final.

Zero-cost edges do not invalidate finite-graph optimality, but they complicate completeness in unbounded state spaces. If infinitely many distinct states are reachable without increasing path cost, the algorithm can spend forever below the goal’s cost. A positive lower bound on step cost, finite reachable sublevel sets, or another progress condition is needed for a useful termination guarantee.

Negative edges expose the exact causal break. Consider S → G with cost 2, S → A with cost 5, and A → G with cost -10. A goal-directed UCS removes G(2) before A(5) and stops, although the later route costs -5. Bellman’s routing formulation belongs to a different relaxation family that can propagate improvements through negative edges when no reachable negative cycle makes the optimum undefined.13

What would weaken the thesis?

A container-only account would be enough if the other fields never changed outcomes

The paper’s broader claim would lose force if state-key changes, replacement policies and goal timing altered only performance while leaving reachability and optimality intact. The controlled counterexamples above show the opposite: each can change the returned answer or termination. The claim is narrower than “everything matters”. It is that these fields are causally load-bearing whenever repeated states, unequal costs or early stopping enter the problem.

Explicit failure boundary

Where this frontier family stops being enough

The template in this paper assumes an enumerable discrete state space, explicit successor generation, static transitions and a path ranking that can be compared consistently. Negative edges require a different relaxation regime such as Bellman-Ford. Adversarial choices require minimax-style backup. Stochastic transitions require expected-value or policy methods. Continuous spaces require discretisation, sampling or optimisation. A changing live environment requires replanning and state reconciliation. Renaming these problems “search” does not preserve the classical guarantees.

Illustrative memory growth for breadth-first and depth-first search A logarithmic chart for a complete ternary tree shows breadth-first frontier size growing from one to 6561 by depth eight, while a depth-first active stack grows roughly linearly. Breadth buys coverage with a frontier; depth buys memory with commitment. 1101001,00010,000100,000 012345678 depth pending or active records · log scale BFS frontier ≈ 3^depthDFS active path + siblings At depth 8, a complete ternary level contains 6,561 nodes. The chart is derived, not benchmarked.
Figure 6. The memory wall is made by the wavefront.For a complete tree with branching factor three, the BFS frontier follows the size of the next level. DFS keeps one active branch plus enough siblings to backtrack. Real graphs may merge, prune or vary in branching, so the curves are structural illustrations rather than runtime predictions.Derived illustrative values: breadth-first level size is 3d; the DFS line represents a simple stack upper profile.

Write the frontier contract before choosing the container

In production-shaped problems, search rarely fails because an engineer forgot that BFS uses a queue. It fails because the state left out a permission, the cost mixed incomparable objectives, the world changed during exploration, or a result was treated as authorised action. The remedy is to define a search contract before implementation.

The contract has seven fields: state identity, successor semantics, goal certification, path-cost algebra, eligibility key, dominance and resource boundary. A container then becomes a consequence of the contract. A deque suits FIFO breadth-first traversal; Python documents deque as supporting fast appends and pops from either side.12 A min-heap suits cost ordering because its smallest element is always at the root.11

Make cutoff, failure and success different outputs

A bounded search can end in at least three states. Success means a result met its certification rule. Failure means the frontier was exhausted under the declared model, so no reachable goal exists within that model. Cutoff means exploration stopped because a depth, cost, memory, expansion or wall-clock boundary was reached. Treating cutoff as failure converts a resource shortage into a false claim about the state space.

The distinction becomes operational in services. A search response should include the stop reason, expansion count, peak frontier size, best pending key, model version and a compact trace of dominance decisions. For UCS, record how many stale entries were skipped and how often a state was improved. For DFS, record maximum depth and cycle-pruning events. For BFS, record the deepest fully completed layer. These signals explain what the algorithm actually established.

Test invariants, not only example routes

A strong test suite contains metamorphic properties. Multiplying every positive edge cost by the same constant must not change a UCS route, though its reported cost scales. Reordering successors may change DFS but should not change BFS depth optimality or UCS cost optimality. Replacing all edge costs with one should make UCS and BFS agree on optimal depth, subject to tie-breaking. Adding a dominated expensive route to an existing state should not change the answer. Introducing a negative edge should trigger rejection or a different algorithm, not silent reuse of a Dijkstra-style guarantee.

Representation tests are equally important. Split one state into two copies with identical successors and costs: the result should remain equivalent. Merge two states whose available actions differ: a sound test should expose the changed reachability. These checks target the contract’s causal assumptions rather than memorising one golden traversal order.

A production-shaped recovery search separated from action authority A search graph proposes recovery sequences across service states. A hard authority boundary separates path selection from live execution. Typed actions are checked, executed and read back before world state is updated. Search may propose a route through recovery states. It does not authorise the effects. MODELLED SEARCH SPACE LIVE AUTHORITY degraded servicereplica lag high isolate write trafficg += 2 min · reversible writes isolatedstate includes receipt promote replicag += 5 min · approval needed replica promotednot yet verified readback and reconcileg += 3 min · deterministic check verified servicegoal predicate true Deterministic authority kernel 1validate typed action 2check identity and policy 3confirm live preconditions 4execute and capture receipt 5read back verified outcome No path record crosses this boundary as ambient authority.
Figure 7. Search proposes; an authority kernel acts.A recovery planner can explore sequences and rank them by a declared additive cost. Live effects still require current identity, permission, precondition checks, receipts and readback. The verified outcome, not the plan text, updates authoritative world state.Worked scenario and practitioner synthesis. It is not presented as a client deployment.
Worked example 2 · Production-shaped scenario

Recovery planning without delegating authority

A service-recovery planner models states such as “writes isolated”, “replica promoted” and “readback passed”. Actions carry estimated duration and hard preconditions. UCS can propose the least-duration sequence only if duration is additive and estimates are acceptable for routing. The planner returns a typed proposal. A deterministic executor checks live state, identity, approval and idempotency before each effect, then records an effect receipt and independent readback. If live state differs, the plan is invalidated and search restarts from reconstructed state. A cheapest path through a model is not evidence that its actions are permitted or that the world followed it.

The frontier-contract audit

The audit below is deliberately mechanism-first. It does not begin with “BFS or DFS?” It begins with the facts that make one of those labels meaningful.

The seven-part frontier contract audit A radial instrument places the search decision at the centre and surrounds it with seven fields: state identity, successor semantics, cost algebra, eligibility key, dominance, goal timing and resource boundary. Audit the invariant before selecting the data structure. FRONTIER CONTRACT what may safely be postponed? State identitysame future? Successorslegal transitions? Cost algebrawhat composes? Eligibility keywho goes next? Dominancewhich record wins? Goal timingnoticed or certified? Resource boundwhen must it stop? A named algorithm is safe only when all seven fields support the intended guarantee.
Figure 8. The frontier-contract audit.Each field can independently invalidate the result. The audit turns algorithm choice into a reviewable statement of assumptions rather than a data-structure reflex.Original practitioner instrument developed for this paper.
1. State identity

List every fact that changes legal successors, goal status or future path cost. Define a canonical, hashable key.

2. Successor semantics

State preconditions, deterministic or uncertain effects, and whether the graph is static during a run.

3. Goal certification

Define the predicate and whether it is safe on generation, required on removal, or needs external verification.

4. Path-cost algebra

Name units, composition rule, lower bounds and whether cost is scalar, constrained, lexicographic or multi-objective.

5. Eligibility key

Specify depth, recency, accumulated cost or another key, including a deterministic tie-break.

6. Dominance and reopening

Define when one path record supersedes another and how stale frontier entries are detected.

7. Resource boundary

Set expansion, memory, depth, cost and wall-clock limits, plus the meaning of cutoff versus failure.

Decision output

Record the guarantee: reachability, fewest steps, least declared cost, first-found route, or bounded best effort.

Decision rule

Do not ask “which search is fastest?” in the abstract

Ask which paths must remain eligible to preserve the decision. Then choose the weakest frontier discipline that preserves them within the available memory and time. A faster search that changes the objective is not an optimisation.

Executable artefact: one template, three disciplines

The Python template below exposes the choices that textbook wrappers often hide. A discipline supplies the priority key and the value used for dominance. The loop tests goals only after removal, rejects negative UCS edges, stores parent records and tolerates stale heap entries. The positive case shows BFS choosing fewer steps while UCS chooses lower cost. The negative case verifies that UCS refuses an invalid cost model.

Python · frontier_contract.py

.

from __future__ import annotations

from dataclasses import dataclass
from heapq import heappop, heappush
from itertools import count
from math import inf
from typing import Callable, Hashable, Iterable, Literal, TypeVar

State = TypeVar("State", bound=Hashable)
Mode = Literal["bfs", "dfs", "ucs"]


@dataclass(frozen=True)
class Edge:
    target: State
    cost: float = 1.0
    action: str = "move"


@dataclass(frozen=True)
class Node:
    state: State
    parent: "Node | None"
    action: str | None
    depth: int
    g: float


def priority(mode: Mode, node: Node, serial: int) -> tuple[float, int]:
    if mode == "bfs":
        return (node.depth, serial)          # shallowest, oldest first
    if mode == "dfs":
        return (-node.depth, -serial)        # deepest, newest first
    return (node.g, serial)                  # cheapest, oldest first


def record_value(mode: Mode, node: Node) -> float:
    return node.g if mode == "ucs" else node.depth


def is_better(mode: Mode, new: float, old: float) -> bool:
    if mode == "ucs":
        return new < old                    # reopen on cheaper path
    return False                             # BFS/DFS keep first discovery


def reconstruct(node: Node) -> tuple[list[State], list[str], float]:
    states: list[State] = []
    actions: list[str] = []
    cursor: Node | None = node
    while cursor is not None:
        states.append(cursor.state)
        if cursor.action is not None:
            actions.append(cursor.action)
        cursor = cursor.parent
    return states[::-1], actions[::-1], node.g


def search(
    start: State,
    is_goal: Callable[[State], bool],
    expand: Callable[[State], Iterable[Edge]],
    mode: Mode,
    *,
    max_expansions: int = 100_000,
) -> tuple[list[State], list[str], float] | None:
    serials = count()
    root = Node(start, None, None, 0, 0.0)
    frontier: list[tuple[float, int, Node]] = []
    first_serial = next(serials)
    heappush(frontier, (*priority(mode, root, first_serial), root))

    best: dict[State, float] = {start: record_value(mode, root)}
    expansions = 0

    while frontier:
        _, _, node = heappop(frontier)
        current = record_value(mode, node)

        # Lazy deletion: ignore a path superseded after it entered the heap.
        if current != best.get(node.state):
            continue

        # Pop-time testing is safe for all three modes used here.
        if is_goal(node.state):
            return reconstruct(node)

        expansions += 1
        if expansions > max_expansions:
            raise RuntimeError("search cutoff: expansion budget exhausted")

        for edge in expand(node.state):
            if mode == "ucs" and edge.cost < 0:
                raise ValueError("UCS requires nonnegative edge costs")

            child = Node(
                state=edge.target,
                parent=node,
                action=edge.action,
                depth=node.depth + 1,
                g=node.g + edge.cost,
            )
            value = record_value(mode, child)
            old = best.get(child.state, inf)

            if child.state not in best or is_better(mode, value, old):
                best[child.state] = value
                serial = next(serials)
                heappush(frontier, (*priority(mode, child, serial), child))

    return None


GRAPH = {
    "S": [Edge("A", 6, "powered door 1"), Edge("B", 1, "aisle 1")],
    "A": [Edge("G", 6, "powered door 2")],
    "B": [Edge("C", 1, "aisle 2")],
    "C": [Edge("G", 1, "aisle 3")],
    "G": [],
}


def expand(state: str) -> Iterable[Edge]:
    return GRAPH[state]


bfs = search("S", lambda s: s == "G", expand, "bfs")
ucs = search("S", lambda s: s == "G", expand, "ucs")

assert bfs == (["S", "A", "G"], ["powered door 1", "powered door 2"], 12.0)
assert ucs == (["S", "B", "C", "G"], ["aisle 1", "aisle 2", "aisle 3"], 3.0)

BAD = {"S": [Edge("G", 10), Edge("A", 1)], "A": [Edge("G", -5)], "G": []}
try:
    search("S", lambda s: s == "G", lambda s: BAD[s], "ucs")
except ValueError as exc:
    assert "nonnegative" in str(exc)
else:
    raise AssertionError("negative UCS edge was not rejected")

print("positive:", bfs, ucs)
print("negative: invalid UCS cost model rejected")

Expected output: BFS returns the two-action path with cost 12; UCS returns the three-action path with cost 3; the negative-edge graph is rejected before it can silently produce an invalid guarantee.

Why the template stores stale records instead of decreasing a heap key

Python’s standard heap does not provide an indexed decrease-key operation. Removing an arbitrary pending record would disturb heap invariants. The template therefore inserts the improved record and updates best[state]. When an older record reaches the top, its stored value no longer equals the current representative and it is skipped. This is the same lazy-removal family described in the official priority-queue implementation notes.11

Frontier laboratory

Step through the same weighted graph. The display exposes the frontier order, expanded set and current path record. JavaScript is optional; the article and Python artefact remain complete without it.

Interactive weighted search graph A start node connects to a short expensive route, a longer cheap route and a deep side branch. Node styles update as search expands. 611611111 S A B X C Y Z G
FrontierS(d0,g0)
Expanded∅
Last removalnone
Resultpending
The initial record is alive on the frontier.
Claim boundary

Established mechanism, new operating instrument

The relationships among BFS, DFS, Dijkstra-style UCS, frontier ordering and graph traversal are established computer-science results.3, 4, 6, 8 The seven-field frontier contract is a practitioner synthesis introduced here to make hidden assumptions reviewable. It is an engineering instrument, not a new optimality theorem.

How the claim changes under pressureChart · state
stateDiagram-v2
  [*] --> Claim
  state "A node is a path record, not a<br/>state" as Claim
  state "The problem contract precedes<br/>the frontier contract" as Test
  state "Tree search and graph search<br/>are different evidence<br/>policies" as Revision
  Claim --> Test: expose mechanism
  Test --> Decision: evidence survives
  Test --> Revision: rival survives
  Revision --> Claim: narrow or rebuild
  Decision --> [*]
The revision loop begins with A node is a path record, not a state and keeps counterexamples inside the reasoning path. Reading use: follow the relation, then test it against the surrounding evidence and limits.

Compact glossary

State
The information required to determine relevant successors, goal status and future cost.
Node
A path record containing a state plus parent, action, depth and accumulated cost.
Frontier
Generated path records that remain eligible for future expansion.
Expansion
Removing a record and generating child records through legal successor actions.
Dominance
A rule that permits one path record to replace or eliminate another without losing the target guarantee.
Reopening
Allowing a state to become active again because a better path record has been found.
Settled
A state whose removed UCS record has an optimal cost under the algorithm’s assumptions.
Cutoff
A search stopped by a resource or depth limit. It is not evidence that no solution exists.

Choose the invariant before the container

The lasting lesson is not that a queue gives breadth and a stack gives depth. It is that frontier ordering is a claim about which unfinished paths can safely wait. Breadth-first search lets expensive paths wait only when depth is the objective. Uniform-cost search lets higher-cost paths wait because nonnegative extension cannot make them cheaper than a settled record. Depth-first search lets almost every sibling wait, accepting order sensitivity in exchange for a small active frontier.

The architecture decision therefore changes. Do not select BFS, DFS or UCS from the visible shape of a problem. Define state identity, successor semantics, path-cost algebra, dominance and goal timing first. Then select the frontier key and data structure that preserve the intended result. Add a resource boundary that distinguishes cutoff from failure.

A search result is defensible when its path was found under a declared frontier contract, its guarantee survives every assumption in that contract, and any live action remains behind an independent authority and verification boundary.

The evidence conversationChart · sequence
sequenceDiagram
  participant Q as Question
  participant M as Mechanism
  participant E as Evidence
  participant D as Decision
  Q->>M: The route that changed when nothing moved
  M->>E: A node is a path record, not a state
  E-->>M: compare rival explanations
  E->>D: References and source ledger
  D-->>Q: revise scope or act
Question, mechanism, evidence and decision exchange the burdens developed in Search as structured exploration. Reading use: follow the relation, then test it against the surrounding evidence and limits.

References and source ledger

Source classUsed forBoundary
Primary researchHistorical algorithms, shortest-path assumptions, depth-first graph algorithms and iterative deepeningOriginal results do not supply this paper’s production audit or authority pattern
Authoritative synthesisUnified forward-search template, definitions and algorithm relationshipsTextbook abstractions assume a declared problem model
Official implementation documentationHeap invariants, stable tie-breaking, lazy removal and deque operationsContainer behaviour alone does not establish search correctness
Practitioner synthesisSeven-field frontier contract and separation of search proposal from live authorityDecision instrument requires validation in each domain
  1. Moore, E. F. (1959). The Shortest Path Through a Maze. Proceedings of the International Symposium on the Theory of Switching, Part II, pp. 285-292. Bibliographic record.
  2. Lee, C. Y. (1961). “An Algorithm for Path Connections and Its Applications.” IRE Transactions on Electronic Computers, EC-10, 346-365. IEEE record.
  3. Dijkstra, E. W. (1959). “A note on two problems in connexion with graphs.” Numerische Mathematik, 1, 269-271. DOI.
  4. Tarjan, R. (1972). “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing, 1(2), 146-160. DOI.
  5. Korf, R. E. (1985). “Depth-first iterative-deepening: An optimal admissible tree search.” Artificial Intelligence, 27(1), 97-109. DOI.
  6. LaValle, S. M. Planning Algorithms, section 2.2.1, “General Forward Search”. Open book section.
  7. LaValle, S. M. Planning Algorithms, section 2.2.4, “A Unified View of the Search Methods”, and section 2.3.3, “Dijkstra Revisited”. Unified view; Dijkstra revisited.
  8. AIMA code project. aima/search.py, reference implementations of depth-first, breadth-first, best-first and uniform-cost search. Source repository.
  9. Black, P. E., ed. “depth-first search.” NIST Dictionary of Algorithms and Data Structures. NIST entry.
  10. Black, P. E., ed. “Dijkstra’s algorithm.” NIST Dictionary of Algorithms and Data Structures. NIST entry.
  11. Python Software Foundation. heapq, “Heap queue algorithm”, including priority-queue implementation notes. Official documentation.
  12. Python Software Foundation. collections.deque. Official documentation.
  13. Bellman, R. (1958). “On a Routing Problem.” Quarterly of Applied Mathematics, 16(1), 87-90. DOI.
  14. Bai, Y., Thiébaux, S. & Trevizan, F. (2025). “Learning Efficiency Meets Symmetry Breaking.” Recent primary planning research on learned guidance, action pruning and state pruning; cited with its graph-expressivity, computational-cost and domain-variance boundaries.