Skip to content
← Production DevOps & SRE

Learning bite

Graphs, BFS, and DFS for dependency questions

Traverse a declared dependency model without confusing reachability with root cause.

  • Python
Documentation reviewed2026-10-01 · 3 min read
  1. Why
  2. Predict
  3. Try
  4. Inspect
  5. Explain
  6. Save evidence

Predict before you try

Can a result that works for one small example establish the algorithm or design works at its boundary?

Try it: Practise boundaries →
On this page

Give every edge one meaning

For this exercise, A -> B means B could be affected if A becomes unavailable. The small graph below is a teaching model inspired by the application. It does not discover MicroBank’s full architecture automatically. An Accounts database failure can affect Accounts; whether a user journey fails also depends on retries, queues, caches, and which operation was attempted.

Breadth-first search (BFS) uses a queue and finds minimum edge counts from a starting point in an unweighted graph. Depth-first search (DFS) follows branches using a stack. Neither discovers incident causality from a diagram alone. Reversing all edges answers a different question.

Traverse a small model

Save as graph_walk.py. Every vertex must be a key, including vertices with no outgoing edges.

python
from collections import deque


def validate(graph, start):
    if start not in graph:
        raise ValueError("Unknown starting vertex")
    if any(v not in graph for edges in graph.values() for v in edges):
        raise ValueError("Every referenced vertex must be declared")


def bfs_distances(graph, start):
    validate(graph, start)
    distance = {start: 0}
    queue = deque([start])
    while queue:
        current = queue.popleft()
        for neighbour in graph[current]:
            if neighbour not in distance:
                distance[neighbour] = distance[current] + 1
                queue.append(neighbour)
    return distance


def dfs_reachable(graph, start):
    validate(graph, start)
    seen = {start}
    stack = [start]
    while stack:
        current = stack.pop()
        for neighbour in graph[current]:
            if neighbour not in seen:
                seen.add(neighbour)
                stack.append(neighbour)
    return seen


impact = {
    "accounts-db": ["accounts"],
    "accounts": ["deposit-journey"],
    "ledger-db": ["ledger"],
    "ledger": ["deposit-journey"],
    "deposit-journey": [],
}
assert bfs_distances(impact, "accounts-db") == {
    "accounts-db": 0, "accounts": 1, "deposit-journey": 2,
}
assert dfs_reachable(impact, "accounts-db") == {
    "accounts-db", "accounts", "deposit-journey",
}

With adjacency lists, validation and traversal take O(V + E) work; the extra queue/stack and visited structure use O(V) space. A visited marker prevents repeated expansion in a cycle. It does not identify whether a directed cycle exists: directed cycle detection needs additional state, such as an active DFS path or the indegree method in the next bite.

Follow the queue and stack

For BFS starting at accounts-db, the queue first contains only that vertex at distance zero. Removing it discovers accounts at distance one. Removing accounts discovers deposit-journey at distance two. A second route to an already discovered vertex does not enqueue it again. Marking it when enqueued avoids duplicate pending work and preserves the first minimum-hop distance.

Run python3 graph_walk.py in the exercise directory, then add a disconnected vertex metrics: [] to the fixture. It should not appear in the result from accounts-db. Add a self-loop to the starting vertex in a scratch copy: visited tracking still stops repeated expansion. Returning successfully does not mean the graph is acyclic; the next bite answers that different question.

Practise boundaries

Test a disconnected vertex, a self-loop, two routes to one vertex, and an unknown neighbour. A shortest hop count is not the lowest-latency network path when edges have different weights. During an incident, use this graph to guide investigation; reachability alone is not a reason to restart a service.

Checkpoint and revision

Draw the edge direction before running either function. Explain why BFS marks a vertex when enqueuing it. For an observed Accounts error, list what logs, timestamps, and request evidence you would need before calling the database the cause.

Checkpoint answer: graph distance is a count of edges under this declared model, not measured network latency. To blame the database for an Accounts error, correlate the failing request with the actual database target, error/wait, and time window. Keep the graph as an investigation map while testing the hypothesis.

Sources

MIT BFS↗, MIT DFS↗, and Python deque↗.

Prove what you learned

Inspect the result

Retain input/assumptions, expected result, actual trace and complexity or design limits.

Explain it in your own words

Explain why your observation supports the stated outcome, and name one thing it does not prove.

Try a small variation

Change one boundary condition—empty input, duplicate value, interval edge or missing dependency—and explain the outcome.

Your evidence should show

  • Record the context/revision or label the work as a paper or prepared fixture.
  • Compare the prediction with the actual result from “Practise boundaries”.
  • Retain input/assumptions, expected result, actual trace and complexity or design limits.
  • Explain the transfer challenge and retain the same verification question.
Hint

State the input assumptions and trace an empty, repeated or limiting case.

Answer guide: compare after predicting

No. Explain correctness and cost or the design trade-off, and compare a declared boundary case.

Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.

Recall bounded loops, data types and explicit failure handling in Python. Revisit the previous lesson.

Save the prediction, result and explanation ↓

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.