Search algorithms are the engine room of artificial intelligence. From GPS navigation finding the fastest route to AlphaGo deciding its next move against a world champion, search is how AI systems explore possibilities and find solutions. Understanding **search algorithms in AI** isn’t just academic—it’s the foundation for building systems that can plan, reason, and solve problems autonomously [citation:1].
This guide breaks down the three most fundamental search strategies: Breadth-First Search (BFS), Depth-First Search (DFS), and A* Search. You’ll learn how each works, when to use which, and why A* has been the gold standard for pathfinding since 1968 [citation:1].
The Building Blocks: State Spaces and Search Problems
Before diving into algorithms, you need to understand what they’re searching through. Every search problem can be modeled with four components [citation:1]:
- State Space: All possible configurations the problem can be in. For a GPS, this is every possible location on the map. For a puzzle, every possible arrangement of pieces.
- Initial State: Where the search begins—your starting location or the puzzle’s starting arrangement.
- Goal State: The target configuration—your destination or the solved puzzle.
- Actions and Transition Model: What moves are possible from any given state, and what new state results from taking an action.
Search algorithms explore this state space by building a tree of possibilities. The root is the initial state, branches are actions, and nodes are the resulting states. The algorithm’s job is to find a path from root to a goal state—ideally the shortest or cheapest path [citation:5].
Uninformed vs. Informed Search: Know the Difference
AI search algorithms fall into two broad categories [citation:2]:
| Uninformed (Blind) Search | Informed (Heuristic) Search |
|---|---|
| Explores using only problem definition—no “hints” about where the goal might be | Uses domain-specific knowledge (heuristics) to estimate which paths are most promising |
| BFS, DFS, Uniform-Cost Search | A*, Greedy Best-First Search |
| Systematic but can be inefficient on large problems | More efficient when good heuristics are available |
| Example: Exploring a maze with your eyes closed | Example: Exploring a maze while seeing which direction the exit lies |
Breadth-First Search (BFS): Explore Level by Level
Uninformed Queue-Based Complete Optimal (Unweighted)What it does: BFS explores the state space layer by layer—first all states one step from start, then all states two steps away, and so on. It uses a queue (FIFO) to manage the frontier of nodes to explore [citation:1].
How it works: Start with the initial state in the queue. While the queue isn’t empty, remove the front node. If it’s the goal, you’re done. Otherwise, add all its unvisited neighbors to the back of the queue and continue [citation:3].
from collections import deque
def bfs(start, is_goal, get_neighbors):
visited = set([start])
queue = deque([start])
while queue:
current = queue.popleft()
if is_goal(current):
return current # Solution found
for neighbor in get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return None # No solution
When BFS shines:
- Finding the shortest path in an unweighted graph (e.g., minimum number of moves in a puzzle) [citation:5]
- Web crawling (visiting pages level by level)
- Social network analysis (“degrees of separation”)
- Any problem where all actions have equal cost
The tradeoff: BFS guarantees the shortest path in terms of number of steps, but it consumes significant memory—O(b^d) where b is the branching factor and d is the solution depth. For a problem with 10 possible moves per state and a solution 8 steps deep, BFS might store millions of nodes [citation:5].
Real-world example: In the 8-puzzle problem, BFS systematically explores all configurations one move away, then two moves away, eventually finding the optimal 25-move solution after examining 145,605 states [citation:1].
Depth-First Search (DFS): Go Deep Before Wide
Uninformed Stack-Based Complete* Not OptimalWhat it does: DFS plunges as far down a single path as possible before backtracking. It uses a stack (LIFO)—either explicitly or through recursion—to track the current exploration path [citation:1].
How it works: Start at the initial state. Pick one neighbor and recursively explore everything reachable from there before considering any other neighbors. When you hit a dead end, backtrack to the most recent unexplored branch [citation:5].
def dfs(node, is_goal, get_neighbors, visited=None):
if visited is None:
visited = set()
visited.add(node)
if is_goal(node):
return node # Solution found
for neighbor in get_neighbors(node):
if neighbor not in visited:
result = dfs(neighbor, is_goal, get_neighbors, visited)
if result:
return result
return None # No solution in this branch
When DFS shines:
- Memory-constrained environments (DFS only stores the current path, O(bm) space vs BFS’s O(b^d)) [citation:5]
- Problems where any solution is acceptable, not necessarily the shortest
- Topological sorting, cycle detection, and maze generation
- Puzzle solving where the state space is deep but solutions exist at various depths
The tradeoff: DFS can get lost in infinite branches if the state space is unbounded. Even in finite spaces, it may find a long, meandering solution while missing a much shorter one. In the 8-puzzle, DFS once found a 1,157-move “solution” when an optimal 25-move path existed [citation:1].
Critical note: DFS is only complete if the state space is finite and you avoid revisiting states. Without cycle detection, DFS can loop forever [citation:5].
A* Search: The Best of Both Worlds
Informed Priority Queue Complete OptimalWhat it does: A* combines the systematic exploration of BFS with heuristic guidance about which direction leads to the goal. It evaluates nodes using f(n) = g(n) + h(n), where g(n) is the cost from start to current node, and h(n) is an estimate of the cost from current node to goal [citation:3].
How it works: Maintain a priority queue ordered by f(n). At each step, expand the node with the lowest f-value. This balances two competing desires: exploring promising paths (low h) while not abandoning efficient paths (low g) [citation:8].
import heapq
def a_star(start, is_goal, get_neighbors, cost_func, heuristic):
# Priority queue stores (f_score, counter, node) to break ties
counter = 0
open_set = [(0, counter, start)]
heapq.heapify(open_set)
g_score = {start: 0}
visited = set()
while open_set:
f, _, current = heapq.heappop(open_set)
if current in visited:
continue
visited.add(current)
if is_goal(current):
return current
for neighbor in get_neighbors(current):
tentative_g = g_score[current] + cost_func(current, neighbor)
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f_score = tentative_g + heuristic(neighbor)
counter += 1
heapq.heappush(open_set, (f_score, counter, neighbor))
return None
The magic is in the heuristic: The heuristic function h(n) estimates the remaining cost. For A* to be optimal, the heuristic must be admissible—it must never overestimate the true cost. For grid-based pathfinding, Manhattan distance (|x1-x2| + |y1-y2|) is a classic admissible heuristic [citation:7].
When A* dominates:
- GPS and navigation systems (finding shortest driving routes)
- Video game AI (pathfinding for characters avoiding obstacles) [citation:9]
- Robotics (autonomous navigation on 2D grids) [citation:3]
- Puzzle solving where optimal solutions matter
- Any problem where you can define a reasonable heuristic
Why it's the gold standard: A* expands significantly fewer nodes than BFS while still guaranteeing optimal solutions. In the 8-puzzle with Manhattan distance heuristic, A* expands only a fraction of the nodes BFS does to find the same optimal 25-move solution [citation:7].
BFS vs. DFS vs. A*: Side-by-Side Comparison
Here's how the three algorithms stack up on the metrics that matter for real applications [citation:5]:
| Algorithm | Complete? | Optimal? | Time | Space | Best For |
|---|---|---|---|---|---|
| BFS | ✅ Yes | ✅ Yes (unweighted) | O(b^d) | O(b^d) | Shallow goals, uniform costs |
| DFS | ✅ Yes* | ❌ No | O(b^m) | O(bm) | Deep search, memory constraints |
| A* | ✅ Yes | ✅ Yes** | O(b^d) | O(b^d) | Pathfinding with good heuristics |
*DFS is complete only in finite state spaces with cycle detection. **A* is optimal when using an admissible heuristic. b = branching factor, d = solution depth, m = maximum depth.
Essential A* Concepts: Admissibility and Consistency
A*'s guarantees of optimality depend on two properties of the heuristic function [citation:5]:
- Admissible Heuristic: A heuristic h(n) is admissible if it never overestimates the true cost to reach the goal. For any node n, h(n) ≤ h*(n) where h*(n) is the actual optimal cost. Manhattan distance is admissible for grid movement because you can't reach the goal in fewer steps than the Manhattan distance.
- Consistent (Monotonic) Heuristic: A heuristic is consistent if for every node n and successor n', h(n) ≤ c(n, n') + h(n'). This ensures f-values never decrease along a path. Consistency implies admissibility and enables more efficient implementations.
Beyond Classical Search: The AI Evolution
While BFS, DFS, and A* form the foundation, modern AI has extended search in powerful directions [citation:1]:
- Monte Carlo Tree Search (MCTS): Used by AlphaGo and AlphaZero, MCTS combines random sampling with tree search to handle enormous state spaces like Go. It's why AI can now defeat world champions [citation:1].
- Population-Based Algorithms: Genetic algorithms and particle swarm optimization explore multiple solution candidates simultaneously, useful when the search space is too large for systematic exploration [citation:1].
- Deep Reinforcement Learning: DQN and its successors learn to search implicitly through neural networks, handling raw sensory input like pixels from Atari games [citation:1].
Yet even with these advances, BFS, DFS, and A* remain the building blocks. Understanding them gives you the intuition to grasp why MCTS works and when to apply which technique.
How to Choose: A Practical Decision Tree
- Do all actions have the same cost?
- Yes, and you need the shortest path → BFS
- Yes, but memory is tight and any solution works → DFS
- Do actions have different costs?
- No good heuristic available → Uniform-Cost Search
- Good heuristic available → A*
- Is the state space massive or infinite?
- Consider local search (Hill Climbing, Simulated Annealing) or MCTS
- Is this a two-player competitive game?
- Use Minimax with Alpha-Beta Pruning [citation:5]
Search Is Fundamental
Search algorithms in AI aren't just interview questions—they're how intelligent systems make decisions. Whether you're building a game, a navigation app, or a puzzle solver, BFS, DFS, and A* are the tools you'll reach for. BFS guarantees the shortest path but consumes memory. DFS sips memory but may wander. A* combines the best of both when you have a good heuristic.
The best way to internalize these algorithms? Implement them. Build a simple grid pathfinder with obstacles. Watch BFS expand in concentric circles. See DFS snake down a single corridor. Observe how A*'s expansion contour bends toward the goal. That visual intuition will serve you long after the pseudocode fades.
Practice with Python → Uninformed Search Deep Dive →