When Google Maps finds the fastest route through traffic, when a chess engine decides its next move, or when a robot navigates around obstacles—they’re all using a search algorithm in AI. At its core, AI search is about systematically exploring possibilities to find a sequence of actions that leads from a starting point to a goal. It’s how machines solve problems without being explicitly programmed for every scenario.
This guide walks through how search algorithms actually work, step by step. You’ll understand the framework that powers everything from GPS navigation to game-playing AI, and you’ll see why search remains fundamental to artificial intelligence even in the age of deep learning.
Step 1: Defining the Search Problem
Before any algorithm can run, the problem must be structured in a way a computer can systematically explore. Every search problem in AI has four essential components:
The State Space
The set of all possible configurations the problem can be in. For a GPS, this is every possible location on the map. For a puzzle like the 8-puzzle, it’s every possible arrangement of the tiles. The state space can be enormous—a chess game has more possible states than atoms in the observable universe—which is why smart search matters.
The Initial State
Where the search begins. Your current location on a map. The starting arrangement of puzzle pieces. The beginning of a maze.
The Goal Test
A function that determines whether a given state is a solution. For navigation, “are we at the destination address?” For chess, “is the opponent in checkmate?” Sometimes there’s one specific goal state; other times, any state satisfying certain conditions works.
Actions and Transitions
What moves are legal from any given state, and what new state results from taking that action. In a maze, the actions are “move up, down, left, right” and the transition model says “if you move up from (3,4), you arrive at (3,5) unless there’s a wall.”
Once these four components are defined, the problem becomes: find a sequence of actions that transforms the initial state into a goal state. That’s exactly what search algorithms do.
Step 2: Building and Exploring the Search Tree
Search algorithms explore possibilities by building a tree. The root node represents the initial state. Each branch represents an action, leading to a child node representing the resulting state. The algorithm’s job is to traverse this tree efficiently until it finds a path to a goal node.
Two critical data structures manage this exploration:
- The Frontier (Open List): Nodes that have been discovered but not yet explored. This is the algorithm’s “to-do list.”
- The Explored Set (Closed List): Nodes that have already been expanded. This prevents infinite loops and redundant work.
The fundamental difference between search algorithms lies in how they manage the frontier—specifically, in what order they remove nodes for expansion.
Step 3: Uninformed Search — Exploring Without Hints
Uninformed (or blind) search algorithms have no information about which states are more promising. They explore systematically based solely on the structure of the search tree.
Breadth-First Search (BFS)
Queue (FIFO) Complete Optimal for UnweightedHow it works step by step:
- Place the initial state in the queue.
- Remove the front node from the queue. If it’s a goal, return the solution.
- Generate all successor nodes (neighbors) of the current node.
- For each successor not already explored, add it to the back of the queue.
- Repeat from step 2 until the queue is empty (no solution) or goal found.
Visual intuition: BFS explores in concentric rings around the start state. First all states 1 step away, then all states 2 steps away, and so on. This guarantees the shortest path in terms of number of steps, but the frontier can grow exponentially.
from collections import deque
def bfs(start, is_goal, get_neighbors):
visited = set([start])
queue = deque([(start, [start])]) # Store path with node
while queue:
node, path = queue.popleft()
if is_goal(node):
return path
for neighbor in get_neighbors(node):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None
Depth-First Search (DFS)
Stack (LIFO) Memory Efficient Not OptimalHow it works step by step:
- Place the initial state on the stack.
- Pop the top node from the stack. If it’s a goal, return the solution.
- Generate all successor nodes.
- Push unvisited successors onto the stack (order determines exploration order).
- Repeat from step 2 until stack is empty or goal found.
Visual intuition: DFS plunges down a single path as far as possible before backtracking. It uses far less memory than BFS because it only stores the current path, not an entire frontier level. However, it may find a long, meandering solution while a much shorter path exists.
def dfs(node, is_goal, get_neighbors, visited=None, path=None):
if visited is None:
visited = set()
path = []
visited.add(node)
path = path + [node]
if is_goal(node):
return path
for neighbor in get_neighbors(node):
if neighbor not in visited:
result = dfs(neighbor, is_goal, get_neighbors, visited, path)
if result:
return result
return None
Step 4: Informed Search — Using Heuristics to Guide Exploration
Informed search algorithms use problem-specific knowledge—a heuristic function—to estimate which paths are most promising. This dramatically reduces the number of states explored.
A* Search: The Gold Standard
Priority Queue Heuristic-Guided OptimalHow it works step by step:
- Place the initial state in a priority queue, ordered by f(n) = g(n) + h(n).
• g(n) = actual cost from start to current node
• h(n) = estimated cost from current node to goal (the heuristic) - Remove the node with lowest f(n) from the priority queue. If it’s a goal, return the solution.
- Generate all successors.
- For each successor:
- Calculate tentative g = g(current) + cost(current, successor)
- If this path is better than any previously known path to successor, update g(successor) and f(successor)
- Add to priority queue (or update its position if already present)
- Repeat from step 2 until queue is empty or goal found.
import heapq
def a_star(start, is_goal, get_neighbors, cost_func, heuristic):
counter = 0 # For tie-breaking
open_set = [(heuristic(start), counter, start, [start], 0)]
heapq.heapify(open_set)
visited = {}
while open_set:
f, _, node, path, g = heapq.heappop(open_set)
if node in visited and visited[node] <= g:
continue
visited[node] = g
if is_goal(node):
return path
for neighbor in get_neighbors(node):
new_g = g + cost_func(node, neighbor)
new_f = new_g + heuristic(neighbor)
counter += 1
heapq.heappush(open_set, (new_f, counter, neighbor,
path + [neighbor], new_g))
return None
The magic of the heuristic: The heuristic h(n) estimates the remaining cost. For A* to guarantee optimal solutions, h(n) must be admissible—it must never overestimate the true cost. For grid pathfinding, Manhattan distance is the classic admissible heuristic: you can't reach the goal in fewer steps than the sum of horizontal and vertical distances.
Step 5: Beyond Systematic Search
When state spaces become enormous (like Go, with more possible board positions than atoms in the universe), even A* becomes impractical. Modern AI uses more sophisticated approaches:
| Algorithm | How It Works | Famous Use Case |
|---|---|---|
| Monte Carlo Tree Search (MCTS) | Combines random sampling with tree search. Plays out many random games from the current position, then focuses search on the most promising moves. | AlphaGo, AlphaZero |
| Genetic Algorithms | Maintains a population of candidate solutions. Combines and mutates the best ones to evolve toward optimal solutions. | Antenna design, scheduling problems |
| Particle Swarm Optimization | Simulates a flock of particles moving through the search space, sharing information about good positions. | Neural network training, engineering optimization |
How to Choose the Right Search Algorithm
Use this decision framework when facing a search problem:
| Your Situation | Recommended Algorithm | Why |
|---|---|---|
| All actions cost the same, need shortest path | BFS | Guarantees optimal step count |
| Memory is extremely limited, any solution works | DFS | Uses minimal memory, finds some solution |
| Actions have different costs, no good heuristic | Uniform-Cost Search | Finds optimal cost path without heuristics |
| You have a good estimate of remaining cost | A* | Dramatically more efficient than blind search |
| State space is astronomically large | MCTS or Genetic Algorithm | Systematic search is impossible |
Common Search Algorithm Mistakes
- Forgetting cycle detection: Without tracking visited states, DFS can loop forever. Always maintain an explored set.
- Using an inadmissible heuristic with A*: If your heuristic overestimates, A* may return suboptimal paths. Verify admissibility.
- BFS on large branching factors: BFS memory usage is O(b^d). For b=10 and d=10, that's 10 billion nodes—your machine will crash.
- Treating all problems as search problems: Not everything requires search. If you can compute the answer directly with math, do that instead.
Search Is Problem-Solving, Automated
Every search algorithm in AI follows the same pattern: define the state space, start at the initial state, systematically explore using a frontier, and stop when the goal is found. The differences—queue vs. stack, informed vs. uninformed—are about efficiency, not concept. Once you understand this framework, you can approach any AI search problem with confidence.
The best way to internalize these algorithms? Build a simple maze solver. Implement BFS and watch it expand outward in perfect circles. Implement A* with Manhattan distance and see how it "knows" which direction to explore. That hands-on intuition is worth a thousand textbook pages.
Practice with Python → Uninformed Search Deep Dive →