DSA

Part 18 — Graphs

A Staff-level coding interview guide to graph modeling, traversal, BFS, DFS, connectivity, shortest paths, complexity, and practical Python problem-solving.

Deepak Mishra16 min read


Graphs are one of the most important abstractions in algorithmic problem solving.

They allow us to represent relationships:

People connected to people
Services connected to services
Cities connected by roads
Machines connected through networks
Pages connected by links
States connected by transitions

The interview-preparation material places Graphs in the advanced algorithmic sequence alongside recursion, Dynamic Programming, and Greedy Algorithms. It also identifies graph modeling as a core algorithmic skill.

For a Staff/Principal engineer, the important question is not simply:

“Do you know BFS and DFS?”

It is:

“Can you recognize when a problem is really a graph, model it correctly, choose the right traversal, and explain the trade-offs?”


1. What Is a Graph?

A graph consists of:

Vertices / Nodes
+
Edges / Relationships

For example:

A ─── B
│     │
│     │
C ─── D

Here:

Vertices = {A, B, C, D}

Edges =
(A,B)
(A,C)
(B,D)
(C,D)

The important abstraction is:

Entity

Relationship

Graph

2. Why Graph Modeling Matters

Many interview problems do not explicitly say:

“You are given a graph.”

Instead, they describe relationships.

For example:

A depends on B
B depends on C

can become:

A → B → C

Similarly:

City A connected to City B
City B connected to City C

becomes:

A ─ B ─ C

The Staff-level skill is recognizing the hidden graph.


3. The Graph Recognition Pattern

When reading a problem, ask:

Are there entities?

Are there relationships between them?

Can I represent those relationships as edges?

Is the question about reachability,
connectivity, paths, dependencies, or cycles?

Think Graph

The source’s pattern-recognition map explicitly associates connections with Graphs and hierarchies with trees/DFS/BFS.


4. Directed vs Undirected Graphs

Undirected

If:

A connected to B


then:

```text
A ─ B

The relationship works in both directions.

Directed

If:

A depends on B

then:

A → B

The direction matters.

Always clarify this before implementing.


5. Weighted vs Unweighted Graphs

An edge may simply represent a relationship:

A ─ B

or carry a cost:

A ──5── B

The weight could represent:

distance
latency
cost
time
risk
capacity

This distinction often determines the shortest-path algorithm.


6. Graph Representation

The most common representation for interview problems is an adjacency list.

For:

A ─ B

C

we can store:

graph = {
    "A": ["B", "C"],
    "B": ["A"],
    "C": ["A"],
}

For a directed graph:

graph = {
    "A": ["B"],
    "B": ["C"],
    "C": [],
}

The representation should match the operations the algorithm needs.


7. Adjacency Matrix

Another representation is an adjacency matrix:

    A B C
A   0 1 1
B   1 0 0
C   1 0 0

Conceptually:

matrix[u][v] = 1

if an edge exists.

Matrix:

Space = O(V²)

Adjacency list:

Space = O(V + E)

For sparse graphs, adjacency lists are usually much more memory efficient.


8. BFS — Breadth-First Search

BFS explores a graph level by level.

Start

All immediate neighbors

Neighbors of neighbors

Next level

...

The natural data structure is a queue.

The source explicitly associates queues with BFS and level-order traversal.

In Python:

from collections import deque

def bfs(graph, start):
    visited = {start}
    queue = deque([start])

    while queue:
        node = queue.popleft()

        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

9. Why BFS Uses a Queue

A queue gives:

First discovered

First processed

Therefore nodes are processed in layers:

distance 0

distance 1

distance 2

distance 3

This makes BFS particularly useful for shortest paths in unweighted graphs.


10. BFS Shortest Path

Suppose:

A ─ B ─ D

C ───── D

Starting from A, BFS explores:

A

B, C

D

The first time BFS reaches a node in an unweighted graph, it has found a shortest path in terms of number of edges.

A typical implementation stores distance:

from collections import deque

def shortest_distance(graph, start, target):
    queue = deque([(start, 0)])
    visited = {start}

    while queue:
        node, distance = queue.popleft()

        if node == target:
            return distance

        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, distance + 1))

    return -1

11. DFS — Depth-First Search

DFS explores one path deeply before backtracking.

Conceptually:

Start

Neighbor

Neighbor

Neighbor

Backtrack

DFS can be implemented recursively:

def dfs(graph, node, visited):
    if node in visited:
        return

    visited.add(node)

    for neighbor in graph[node]:
        dfs(graph, neighbor, visited)

Or iteratively:

def dfs(graph, start):
    visited = {start}
    stack = [start]

    while stack:
        node = stack.pop()

        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                stack.append(neighbor)

The source identifies stacks with DFS.


12. BFS vs DFS

Requirement BFS DFS
Data structure Queue Stack / recursion
Level-order exploration Excellent No
Unweighted shortest path Excellent Not generally
Connectivity Yes Yes
Cycle detection Yes Yes
Topological reasoning Sometimes Often useful
Memory shape Frontier Current path

A useful rule:

Shortest number of edges?
→ BFS

Explore structure deeply?
→ DFS

But don’t rely only on memorized rules.

Understand the graph’s structure and the required output.


13. The Most Important Graph Invariant

For traversal, maintain:

visited

The invariant is:

Every node in visited has already been discovered and will not be processed again.

Without this, a cyclic graph can cause repeated traversal:

A → B
↑   ↓
└── C

A traversal without visited-state management can continue indefinitely.


14. Graph Complexity

For an adjacency-list representation:

V = number of vertices
E = number of edges

A complete traversal is typically:

Time  = O(V + E)
Space = O(V)

Why?

Every vertex is discovered at most once, and every adjacency relationship is examined a bounded number of times.

This is the graph analogue of the source’s broader emphasis on explicitly explaining complexity rather than merely saying an algorithm is “fast.”


15. Connected Components

For an undirected graph:

A ─ B       C ─ D

there are two connected components.

Algorithm:

for every unvisited node:
    run BFS/DFS
    count one component

Python:

def count_components(graph):
    visited = set()
    count = 0

    for node in graph:
        if node in visited:
            continue

        count += 1
        stack = [node]
        visited.add(node)

        while stack:
            current = stack.pop()

            for neighbor in graph[current]:
                if neighbor not in visited:
                    visited.add(neighbor)
                    stack.append(neighbor)

    return count

Complexity:

O(V + E)

16. Cycle Detection

Cycles are a fundamental graph property.

Undirected:

A ─ B
│   │
C ──┘

Directed:

A → B → C
↑       ↓
└───────┘

The exact cycle-detection technique depends on whether the graph is directed or undirected.

That distinction is important in interviews.


17. Directed Graph Cycle Detection

For directed graphs, DFS can track the current recursion path.

Conceptually:

visited
+
currently_in_path

If DFS reaches a node already in the current path:

cycle exists

A typical state model is:

0 = unvisited
1 = visiting
2 = completed

If an edge points to a visiting node:

back edge
→ cycle

18. Topological Ordering

Directed acyclic graphs often represent dependencies:

Database

Backend

API

Frontend

A topological ordering gives:

Database
Backend
API
Frontend

such that every dependency appears before the thing that depends on it.

This pattern appears in:

build systems
course prerequisites
package dependencies
workflow execution
deployment pipelines
task scheduling

A crucial condition is:

Topological ordering exists

Directed graph has no cycle

19. Kahn’s Algorithm

One common approach uses indegrees.

For every node:

indegree[node] =
number of incoming edges

Then:

Nodes with indegree 0

Process

Remove outgoing edges

New indegree-0 nodes

Repeat

This naturally uses a queue.

The pattern is:

dependency graph

indegree

queue

topological order

20. DFS Topological Ordering

Another approach is DFS.

Conceptually:

DFS children

finish node

append node

Finally:

reverse finishing order

gives a topological ordering when the graph is acyclic.

The choice between Kahn’s algorithm and DFS is often a good Staff-level discussion point.


21. Shortest Paths

Graph shortest-path problems depend heavily on edge weights.

Unweighted

BFS

Non-negative weighted edges

Dijkstra

Negative edges

Bellman-Ford-style reasoning

DAG

Topological-order shortest path

The important Staff-level skill is not memorizing algorithms independently.

Instead:

Graph properties
+
Edge-weight properties
+
Required output
=
Shortest-path algorithm

22. Dijkstra’s Algorithm

For non-negative edge weights, Dijkstra repeatedly selects the currently closest unprocessed node.

The conceptual structure is:

Start

Best known distance

Choose minimum-distance node

Relax outgoing edges

Update distances

Repeat

A priority queue is the natural implementation tool.

Python:

import heapq

def dijkstra(graph, start):
    distances = {node: float("inf") for node in graph}
    distances[start] = 0

    heap = [(0, start)]

    while heap:
        distance, node = heapq.heappop(heap)

        if distance != distances[node]:
            continue

        for neighbor, weight in graph[node]:
            new_distance = distance + weight

            if new_distance < distances[neighbor]:
                distances[neighbor] = new_distance
                heapq.heappush(heap, (new_distance, neighbor))

    return distances

23. Why Dijkstra Needs Non-Negative Weights

Dijkstra permanently relies on the fact that once the smallest-distance node is selected, a later path cannot make it cheaper through a negative edge.

Negative edges break that assumption.

This is a classic example of:

Algorithm
+
Precondition

A Staff-level engineer should always state the precondition.


24. Graph Search as State-Space Search

A powerful abstraction is that a graph does not always represent physical objects.

Nodes can represent states.

For example:

Chess position
+
Possible move
=
Graph edge

Or:

Configuration
+
Valid transition
=
Graph edge

Then:

Start state

Transitions

Goal state

This turns many apparently unrelated problems into graph-search problems.


25. Grid Problems Are Often Graph Problems

Consider:

0 0 1
0 1 0
0 0 0

Each cell can be treated as a node.

Adjacent cells become edges.

Therefore:

Grid

Implicit graph

BFS / DFS

This is one of the most valuable interview transformations.

You don’t necessarily need to explicitly build the graph.

Instead, generate neighbors dynamically.


26. Implicit Graphs

An implicit graph is one where:

nodes are states
edges are generated by rules

Examples:

word transformations
maze movement
puzzle states
robot positions
game states
configuration transitions

The graph exists conceptually even if no adjacency-list object exists in memory.


27. Graphs vs Trees

A tree is a special kind of graph.

A typical tree has:

one connected component
no cycles

Graphs are more general:

cycles
multiple components
directed edges
weighted edges
arbitrary connectivity

The source’s pattern map separates hierarchical structures from general connections, associating hierarchies with trees and connections with graphs.


28. Graph Modeling Checklist

When you suspect a graph, ask:

□ What are the nodes?

□ What are the edges?

□ Directed or undirected?

□ Weighted or unweighted?

□ Can cycles exist?

□ Is the graph connected?

□ Is the graph sparse or dense?

□ Do I need shortest path?

□ Do I need reachability?

□ Do I need ordering?

□ Do I need connected components?

□ Do I need cycle detection?

□ Is the graph explicit or implicit?

This prevents many implementation mistakes.


29. Staff-Level Pattern Selection

Use:

Need reachability?
→ DFS / BFS

Need shortest unweighted path?
→ BFS

Need connected components?
→ DFS / BFS

Need dependency ordering?
→ Topological sort

Need cycle detection?
→ DFS / indegree reasoning

Need shortest non-negative weighted path?
→ Dijkstra

Need state-space exploration?
→ BFS / DFS

Need hierarchical structure?
→ Tree algorithms

But always validate the constraints and graph properties first.

The source emphasizes exactly this broader principle:

Pattern
+
Constraints
+
Requirements
=
Algorithm choice

30. Brute Force → Graph Modeling

A strong interview approach is:

Brute force

Understand repeated work

Identify entities

Identify relationships

Model as graph

Choose traversal

Optimize

This follows the broader iterative-refinement approach emphasized in the source.


31. Concrete Examples First

Before coding, draw a tiny graph.

For example:

A ─ B ─ D

C ──────┘

Then ask:

What should BFS do?

What should DFS do?

What is the shortest path?

What happens if I add a cycle?

What happens if D is unreachable?

The source recommends using concrete examples—including small and extreme inputs—before generalizing.


32. Graph Edge Cases

Always test:

empty graph
single node
single edge
disconnected graph
self-loop
duplicate edges
cycle
no path
multiple paths
very large graph
dense graph
sparse graph

For directed graphs also test:

DAG
directed cycle
multiple incoming edges
multiple outgoing edges

33. Python Tools for Graph Problems

The source emphasizes fluency with standard Python data structures and specifically associates:

list
set
deque
heapq

with common algorithmic patterns.

For graphs:

set
→ visited

deque
BFS

list
→ adjacency lists / DFS stack

heapq
→ priority queues / Dijkstra

The implementation should make the algorithm obvious.


34. Recursion vs Iteration

DFS is often written recursively:

def dfs(node):
    ...

But recursion depth matters.

The source explicitly warns that recursion stack space counts toward complexity and can become O(n) for a skewed structure.

Therefore, for very large graphs, an iterative DFS may be safer.


35. Complexity Is Part of the Answer

Don’t finish with:

“This should be fast.”

Say:

There are V vertices and E edges.

Each vertex is visited once.
Each edge is examined at most a constant number of times.

Therefore:

Time = O(V + E)

Then discuss:

visited set = O(V)
queue/stack = O(V)

This is the level of precision expected in a strong Staff interview.


36. Graph Interview Communication

The source strongly recommends thinking aloud while exposing decisions rather than narrating every keystroke.

For a graph problem, communicate:

"I'll model each service as a node."

"An allowed dependency becomes a directed edge."

"Because I need the minimum number of transitions,
I'll use BFS."

"I'll mark a node visited when I enqueue it,
so it cannot be inserted repeatedly."

"The traversal is O(V + E)."

That is much stronger than silently writing code.


37. The Staff-Level Graph Mental Model

When a problem appears complicated, ask:

What are the entities?

What relationships connect them?

Can those relationships be edges?

What question are we asking?

Reachability?
Shortest path?
Connectivity?
Cycle?
Ordering?

Choose the graph algorithm.

This transforms a vague problem into a structured algorithmic problem.


38. Part 17 → Part 18

The progression now becomes:

Part 15 — Recursion

Decompose problems

Part 16 — Dynamic Programming

Reuse overlapping subproblems

Part 17 — Greedy Algorithms

Prove safe local choices

Part 18 — Graphs

Model relationships and connectivity

The available source explicitly describes Parts/Chapters 15–18 as the advanced algorithmic portion and identifies graphs as part of that progression.


39. Graph Cheat Sheet

Problem Typical technique
Reachability DFS / BFS
Connected components DFS / BFS
Shortest unweighted path BFS
Cycle detection DFS / indegree
Dependency ordering Topological sort
Non-negative weighted shortest path Dijkstra
Grid traversal BFS / DFS
State-space search BFS / DFS
Dense graph Consider matrix
Sparse graph Usually adjacency list

40. Final Staff-Level Checklist

Before submitting a graph solution, ask:

□ Did I identify the nodes correctly?

□ Did I identify the edges correctly?

□ Directed or undirected?

□ Weighted or unweighted?

□ Can cycles exist?

□ Did I choose BFS or DFS for a reason?

□ Did I maintain visited state?

□ Is the graph explicit or implicit?

□ Did I handle disconnected components?

□ Did I test no-path cases?

□ Did I state time complexity?

□ Did I state auxiliary space?

□ Did I account for recursion depth?

□ Did I explain the invariant?

□ Can I explain why the algorithm is correct?

41. Final Takeaway

Graph problems become much easier when you stop seeing them as isolated coding questions.

Instead:

Entities

Relationships

Graph model

Graph properties

Algorithm

Traversal

Correctness

Complexity

The most valuable Staff-level skill is graph modeling.

Once the model is correct, the algorithm often becomes much easier to see.

And the broader interview principle from the source still applies:

Pattern
+
Constraints
+
Requirements
=
Algorithm choice

That is the mindset that turns graph problems from memorization exercises into structured engineering problems.


Source Note

This article is grounded in the available uploaded interview-preparation material. The material explicitly places Graphs in the advanced algorithmic sequence (Parts/Chapters 15–18), identifies graph modeling as an algorithmic pattern, and connects graphs with relationships/connections and DFS/BFS.

The available uploaded extraction does not expose the complete original book’s numbered Graph problem set. Therefore, this Part 18 article presents the graph concepts that are supported by the available material and clearly distinguishes the broader graph tutorial structure from source-specific numbered problems.