how to represent graphs /trees in python and how to detect cycles?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Python, graphs and trees are usually represented with adjacency lists, edge lists, or node objects, depending on the operations you care about most. Cycle detection then depends on the structure type: for a directed graph you usually track a recursion stack, while for an undirected graph you usually track the parent node during traversal.
Adjacency Lists Are the Usual Default
For most graph algorithms, an adjacency list is the most practical representation. In Python, that usually means a dictionary mapping each node to its neighbors.
This representation is good because it is:
- easy to read
- memory-efficient for sparse graphs
- convenient for traversal algorithms such as DFS and BFS
If your nodes are integers, you can also use a list of lists instead of a dictionary.
Edge Lists and Matrices Have Specific Uses
Sometimes a different representation is more appropriate.
An edge list is simple when you mostly care about the set of connections:
An adjacency matrix is useful for dense graphs or for constant-time edge existence checks:
The tradeoff is space. Matrices use much more memory for sparse graphs, so adjacency lists remain the normal default in Python.
Trees Are Usually Simpler Than General Graphs
A tree is just a special kind of graph:
- connected
- acyclic
- usually hierarchical
In Python, a tree is often represented as a node object with a list of children.
This is natural for recursive traversal and for structures such as file trees, parse trees, and UI hierarchies.
Detect Cycles in a Directed Graph
For a directed graph, a standard technique is depth-first search with two sets:
- '
visitedfor nodes seen at all' - '
in_stackfor the current recursion path'
If DFS re-enters a node that is already in the current path, a cycle exists.
This is the classic answer for cycle detection in directed graphs.
Detect Cycles in an Undirected Graph
For undirected graphs, you need a slightly different rule. Seeing a previously visited node is not automatically a cycle, because you naturally revisit the node you just came from. So you track the parent.
This version matches the structure of undirected edges more accurately.
Trees Should Not Have Cycles
If a structure is supposed to be a tree and your traversal finds a cycle, the structure is invalid as a tree. That often means:
- a parent was linked as a child
- the input data is actually a general graph
- the construction logic introduced a back-reference
So cycle detection is not just an algorithm exercise. It is often a data-validation step.
Choose the Representation Based on the Problem
A useful rule is:
- adjacency list for most graph algorithms
- node objects for recursive tree logic
- edge list for simple storage or import/export
- matrix only when dense connectivity or constant-time edge checks really matter
Python is flexible enough to support all of them, but adjacency lists and node objects cover most real use cases.
Common Pitfalls
- Using an adjacency matrix for a sparse graph and wasting a lot of memory.
- Applying directed-graph cycle logic to an undirected graph and getting false positives.
- Assuming a tree representation cannot contain cycles just because it is called a tree.
- Forgetting to initialize nodes with no outgoing edges in the adjacency list when traversal code expects them.
- Choosing a representation that makes the target algorithm harder instead of easier.
Summary
- In Python, adjacency lists are the most common representation for general graphs.
- Trees are often represented with node objects and child lists.
- Use DFS with a recursion-stack check to detect cycles in directed graphs.
- Use DFS with parent tracking to detect cycles in undirected graphs.
- Pick the representation that matches the operations you need, not just the one that is easiest to type.

