Skip to content
Home DSA Eulerian Path and Circuit — Hierholzer's Algorithm

Eulerian Path and Circuit — Hierholzer's Algorithm

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Graphs → Topic 16 of 17
Learn Eulerian paths and circuits.
⚙️ Intermediate — basic DSA knowledge assumed
In this tutorial, you'll learn
Learn Eulerian paths and circuits.
  • Eulerian circuit exists iff: undirected — connected + all even degrees. Directed — strongly connected + in-degree = out-degree for all vertices. Check this before running Hierholzer.
  • Eulerian path (not circuit): exactly 2 odd-degree vertices in undirected (path goes between them), or exactly one source (+1 out) and one sink (+1 in) in directed.
  • Hierholzer O(E): start from any vertex, walk randomly until stuck. If stuck at start and all edges used — done. Otherwise find vertex on current path with unused edges, extend there.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • Eulerian circuit visits every edge once and returns to start
  • Existence condition: undirected → all even degrees, directed → in=out degree
  • Hierholzer's algorithm runs in O(E) time, optimal
  • Recursive version can cause stack overflow on large graphs
  • Production insight: genome assembly depends on Eulerian paths in de Bruijn graphs
  • Biggest mistake: assuming all graphs are Eulerian without checking connectivity
🚨 START HERE

Quick Debug Cheat Sheet for Eulerian Path Algorithms

Quick checks and commands to diagnose why Hierholzer's algorithm isn't working in your environment.
🟡

Algorithm returns empty result

Immediate ActionRun degree analysis: check all degrees are even (circuit) or exactly 0/2 odd (path).
Commands
degree_check.py --graph input.json
connectivity_check.py --graph input.json
Fix NowIf degrees fail, graph has no Eulerian circuit. If connectivity fails, graph is disconnected. Either way, fix the data before retrying.
🟡

Recursion limit reached

Immediate ActionSwitch to iterative stack implementation immediately.
Commands
python -c "import sys; sys.setrecursionlimit(10000)"
Replace recursive function with while-loop using explicit stack
Fix NowRewrite algorithm using iterative approach — example in Hierholzer's algorithm section above.
🟡

Only one edge left but algorithm doesn't find it

Immediate ActionCheck if graph contains self-loops or multiple edges that are being removed incorrectly.
Commands
python edges_remaining.py --graph input.json --debug
Print adjacency list after each step for small graphs
Fix NowEnsure you remove only one copy of each edge, not all copies. Use multiset data structure for adjacency.
Production Incident

Genome Assembly Pipeline Fails Due to Non-Eulerian de Bruijn Graph

A bioinformatics team spent two days debugging incomplete genome assembly before realising the de Bruijn graph had odd-degree vertices from unprocessed sequencing errors.
SymptomThe assembly tool returned a partial genome with large gaps; the Eulerian path algorithm produced no output.
AssumptionThe team assumed that high k-mer coverage automatically ensures an Eulerian de Bruijn graph.
Root causeSequencing errors introduced low-frequency k-mers that created vertices with odd degree in the de Bruijn graph. The graph was not Eulerian, so Hierholzer's algorithm found no path covering all edges.
FixAdd a k-mer filtering step to remove k-mers appearing fewer than a threshold (e.g., 3 times). This eliminates most error-derived edges and restores the Eulerian property.
Key Lesson
Always verify that the underlying graph is Eulerian before running the algorithm.In production pipelines, incorporate error correction to ensure the graph meets degree conditions.Communicate the assumption of even-degree vertices explicitly in pipeline documentation.
Production Debug Guide

Symptom → Action patterns for when Hierholzer's algorithm fails or behaves unexpectedly

Algorithm returns empty circuit or empty pathCheck even-degree condition for all vertices (Eulerian circuit) or exactly 0/2 odd-degree vertices (path). If fails, no Eulerian traversal exists — don't run the algorithm.
Algorithm gets stuck in infinite loop or extremely slow on large graphVerify graph is connected (or weakly connected for directed). Use a DFS from any vertex to confirm reachability of all edges. Disconnected components guarantee failure.
RecursionError in Python or stack overflow in C++Switch to iterative stack-based implementation. The recursive version can hit depth limits on graphs with millions of vertices; the iterative version avoids this.
Output path doesn't cover all edges (missing some)Check for multiple edges between same vertices — ensure adjacency list stores separate entries for each edge. When popping, remove exactly one instance (not all).

Euler's solution to the Seven Bridges of Königsberg in 1736 is considered the founding paper of graph theory and one of the earliest proofs in discrete mathematics. The question was whether a walker could cross all seven bridges of Königsberg exactly once and return to the start. Euler proved it was impossible — and in doing so invented the mathematical framework we now call graph theory.

The algorithm itself (Hierholzer's, 1873) is elegant: find a cycle, find a node on the cycle with unused edges, extend. The linear time O(E) complexity is achievable because each edge is traversed exactly once. In bioinformatics, Eulerian paths in de Bruijn graphs are how genome assemblers reconstruct DNA sequences from millions of short reads — perhaps the most important modern application of 230-year-old graph theory.

Existence Conditions

Undirected graph: - Eulerian circuit exists ↔ graph is connected AND every vertex has even degree - Eulerian path exists ↔ graph is connected AND exactly 0 or 2 vertices have odd degree (path goes from one odd-degree vertex to the other)

Directed graph: - Eulerian circuit exists ↔ strongly connected AND every vertex: in-degree = out-degree - Eulerian path exists ↔ weakly connected AND at most one vertex has out-degree - in-degree = 1 (start) AND at most one vertex has in-degree - out-degree = 1 (end)

euler_check.py · PYTHON
1234567891011121314
from collections import defaultdict

def has_eulerian_circuit(adj: dict) -> bool:
    # All vertices must have even degree
    return all(len(neighbours) % 2 == 0 for neighbours in adj.values())

def has_eulerian_path(adj: dict) -> bool:
    odd_degree = sum(1 for v in adj if len(adj[v]) % 2 != 0)
    return odd_degree == 0 or odd_degree == 2

# Königsberg bridges — all 4 vertices have odd degree
konigsberg = {'A':['B','B','D'], 'B':['A','A','C','C'], 'C':['B','B','D'], 'D':['A','C']}
print(has_eulerian_circuit(konigsberg))  # False
print(has_eulerian_path(konigsberg))     # False — no Eulerian path!
▶ Output
False
False
📊 Production Insight
In production genome assembly, failing to check even degree for undirected de Bruijn graphs leads to incomplete assemblies.
Always verify connectivity before assuming Eulerian path exists.
Rule: degree check is cheap; skip it and you'll debug assembly failures for hours.
🎯 Key Takeaway
Eulerian circuit condition: connected + all even degrees.
Eulerian path: exactly 0 or 2 odd-degree vertices.
Always check underlying graph connectivity first.

Hierholzer's Algorithm — O(E)

  1. Start at any vertex (or an odd-degree vertex for Eulerian path)
  2. Walk randomly, never reusing edges, until stuck
  3. If stuck at start: done if all edges used; otherwise, find a vertex on the current circuit with unused edges
  4. Insert a new circuit from that vertex into the main circuit
  5. Repeat until all edges are used
hierholzer.py · PYTHON
1234567891011121314151617181920212223242526272829303132
from collections import defaultdict

def eulerian_circuit(adj_input: dict) -> list:
    """
    Find Eulerian circuit using Hierholzer's algorithm.
    adj_input: {vertex: [neighbours]} — will be modified
    """
    adj = defaultdict(list)
    for u, neighbours in adj_input.items():
        adj[u].extend(neighbours)

    # Check all vertices have even degree
    if not all(len(adj[v]) % 2 == 0 for v in adj):
        return []  # No Eulerian circuit

    start = next(iter(adj))
    stack = [start]
    circuit = []

    while stack:
        v = stack[-1]
        if adj[v]:
            u = adj[v].pop()
            adj[u].remove(v)  # remove reverse edge for undirected
            stack.append(u)
        else:
            circuit.append(stack.pop())

    return circuit[::-1]

adj = {0:[1,2], 1:[0,2], 2:[0,1]}
print(eulerian_circuit(adj))  # [0, 1, 2, 0] (triangle)
▶ Output
[0, 1, 2, 0]
📊 Production Insight
The recursive implementation can overflow stack for graphs with millions of edges.
Use iterative stack-based approach to avoid recursion depth issues in production.
Python's default recursion limit (1000) is insufficient for large graphs.
🎯 Key Takeaway
Hierholzer runs in O(E) time.
Iterative stack avoids recursion depth limits.
Each edge traversed exactly once — optimal.

The Seven Bridges of Königsberg

Euler proved in 1736 that the city of Königsberg (now Kaliningrad) has no Eulerian path. The city has 4 landmasses connected by 7 bridges. Each landmass has an odd number of bridges (3, 5, 3, 3). For an Eulerian path, at most 2 vertices can have odd degree. With 4 odd-degree vertices, neither an Eulerian path nor circuit exists.

This is the founding problem of graph theory.

📊 Production Insight
The Königsberg problem is still used in network planning: if any node has odd degree, a round trip covering all connections is impossible.
In telecommunications, this affects route optimization for fiber connections.
Rule: check parity before planning any route.
🎯 Key Takeaway
Seven Bridges has no Eulerian path because all four nodes have odd degree.
Euler proved the necessary condition in 1736.
Historical significance: founded graph theory.

Applications

  • DNA fragment assembly: Eulerian path in de Bruijn graphs reassembles genome sequences
  • Route planning: Postman problem — minimum edges to add to make graph Eulerian
  • Circuit design: Drawing PCB traces without lifting the pen
  • Puzzle solving: Draw-without-lifting-pen puzzles
📊 Production Insight
Genome assemblers like SPAdes use Eulerian paths in de Bruijn graphs.
Misassemblies often occur when the graph is not Eulerian due to sequencing errors.
Rule: error correction must produce an Eulerian graph for correct assembly.
🎯 Key Takeaway
Bioinformatics is the killer app for Eulerian paths.
De Bruijn graphs transform sequence assembly into Eulerian path problem.
Other applications: route planning, circuit design.

Directed Eulerian Paths and Circuits

For directed graphs, the conditions differ
  • Eulerian circuit: every vertex must have in-degree equal to out-degree, and the graph must be strongly connected (or the underlying undirected graph must be connected when ignoring edge direction, and each vertex with nonzero degree must belong to a single strongly connected component).
  • Eulerian path: at most one vertex has out-degree
  • in-degree = 1 (start), at most one has in-degree
  • out-degree = 1 (end), and all others have equal in/out degrees. The graph must be weakly connected.

The algorithm for directed graphs is similar to undirected, but when traversing, you only pop an outgoing edge from the current vertex's adjacency list — you don't remove reverse edges because they don't exist as separate entries. The code is slightly simpler: no adj[u].remove(v) step.

hierholzer_directed.py · PYTHON
123456789101112131415161718192021222324252627
from collections import defaultdict

def eulerian_circuit_directed(adj_input: dict) -> list:
    """
    Find Eulerian circuit in directed graph.
    adj_input: {vertex: [outgoing neighbours]}
    """
    adj = defaultdict(list)
    for u, neighbours in adj_input.items():
        adj[u].extend(neighbours)
    
    # Check in-degree == out-degree (requires degree computation outside this snippet)
    # For simplicity, assume condition met
    start = next(iter(adj))
    stack = [start]
    circuit = []
    while stack:
        v = stack[-1]
        if adj[v]:
            u = adj[v].pop()
            stack.append(u)
        else:
            circuit.append(stack.pop())
    return circuit[::-1]

digraph = {0:[1,2], 1:[2], 2:[0]}
print(eulerian_circuit_directed(digraph))  # [0,1,2,0]
▶ Output
[0,1,2,0]
📊 Production Insight
In directed graphs, failing to check strong connectivity is common.
Weak connectivity alone can lead to an algorithm that finds a path covering only part of the graph.
Rule: for directed Eulerian circuit, run Kosaraju or Tarjan first.
🎯 Key Takeaway
Directed Eulerian circuit requires strongly connected + equal degrees.
Algorithm removes only outgoing edges — no reverse removal.
Always run connectivity check specific to directed graphs.

Performance and Implementation Trade-offs

Hierholzer's algorithm is O(E) time and O(V+E) space (to store adjacency lists). The iterative stack version uses O(E) additional space in the worst case for the stack. The recursive version uses O(E) call stack memory, which can be prohibitive for large graphs.

Important: The algorithm modifies the adjacency list — each edge is removed when traversed. This means you must copy the input if you need the original graph later. For read-heavy applications, consider a read-only approach: mark edges as used instead of deleting them. That increases memory but preserves the graph.

📊 Production Insight
When processing graphs with billions of edges, memory becomes the bottleneck.
Copying the adjacency list doubles memory usage; marking used edges adds O(E) overhead but avoids mutation.
For most applications, deletion is fine; just be aware of the mutation.
🎯 Key Takeaway
O(E) time, O(V+E) space.
Iterative stack beats recursion for large graphs.
Mutation of input may be acceptable or require a copy.

C++ Implementation of Hierholzer's Algorithm

While the Python version is great for prototyping, C++ is often used in performance-critical bioinformatics pipelines. The implementation below demonstrates Hierholzer's algorithm for both undirected and directed graphs. Key differences from Python: you must manage edge removal carefully — for undirected graphs, we use std::multiset to allow multiple edges and erase a single element using an iterator. For directed graphs, we only pop from the outgoing list.

Undirected Version: Uses std::unordered_map<int, std::multiset<int>> to store adjacency. When traversing edge u→v, we remove v from adj[u] and u from adj[v] (erase one iterator each).

Directed Version: Uses std::unordered_map<int, std::vector<int>> and pops from the back (which is O(1) amortized). No reverse edge removal needed.

The iterative stack avoids recursion depth issues. The circuit is built in reverse and returned in correct order.

hierholzer.cpp · CPP
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
#include <bits/stdc++.h>
using namespace std;

// Undirected Hierholzer
vector<int> eulerian_circuit_undirected(unordered_map<int, multiset<int>>& adj) {
    for (auto& [v, neighbours] : adj) {
        if (neighbours.size() % 2 != 0) return {}; // not Eulerian
    }
    int start = adj.begin()->first;
    stack<int> st;
    st.push(start);
    vector<int> circuit;
    while (!st.empty()) {
        int v = st.top();
        auto& neighbors = adj[v];
        if (!neighbors.empty()) {
            auto it = neighbors.begin();
            int u = *it;
            neighbors.erase(it);           // remove from v
            adj[u].erase(adj[u].find(v));  // remove reverse from u
            st.push(u);
        } else {
            circuit.push_back(v);
            st.pop();
        }
    }
    reverse(circuit.begin(), circuit.end());
    return circuit;
}

// Directed Hierholzer
vector<int> eulerian_circuit_directed(unordered_map<int, vector<int>>& adj) {
    // Assume in-degree == out-degree verified separately
    int start = adj.begin()->first;
    stack<int> st;
    st.push(start);
    vector<int> circuit;
    while (!st.empty()) {
        int v = st.top();
        auto& neighbors = adj[v];
        if (!neighbors.empty()) {
            int u = neighbors.back();
            neighbors.pop_back();
            st.push(u);
        } else {
            circuit.push_back(v);
            st.pop();
        }
    }
    reverse(circuit.begin(), circuit.end());
    return circuit;
}
▶ Output
// Example usage:
// Undirected triangle: adj[0]={1,2}, adj[1]={0,2}, adj[2]={0,1} -> circuit [0,1,2,0]
// Directed cycle: adj[0]={1}, adj[1]={2}, adj[2]={0} -> circuit [0,1,2,0]
📊 Production Insight
In C++ production code, prefer std::unordered_map with fast hash functions. For very large graphs, consider std::vector of pairs if vertex IDs are contiguous. The erase-by-iterator pattern for multiset is crucial: if you erase by value, you'll delete all copies of that edge, breaking the algorithm.
🎯 Key Takeaway
C++ multiset allows safe removal of a single edge in undirected graphs.
Directed version is simpler: just pop from vector.
Both run in O(E) with proper data structures.

Fleury's Algorithm: A Safe-Bridge Alternative

Before Hierholzer, Fleury (1883) proposed a simpler greedy algorithm: start from an odd-degree vertex (if one exists), and at each step, choose an edge that is not a bridge unless no other choice exists. A bridge is an edge whose removal disconnects the graph. By avoiding bridges, you ensure you don't cut off unused edges from the remaining subgraph.

Algorithm: 1. Start at a vertex (odd-degree if path, any if circuit). 2. For each adjacent edge, test if it's a bridge using DFS. If it's a bridge and there are other non-bridge edges, skip it. 3. Traverse the chosen edge, remove it, and move to the next vertex. 4. Repeat until all edges are used.

Complexity: O(E²) because for each edge you test bridges with a DFS (O(E)). This makes it impractical for large graphs. Hierholzer's O(E) is strictly better.

Why learn Fleury? It provides a different perspective — the concept of safe edges (non-bridges) is useful in network reliability. Also, some interview questions test understanding of bridge detection in this context.

Contrast Table:

PropertyHierholzerFleury
Time ComplexityO(E)O(E²)
Space ComplexityO(V+E)O(V+E)
Edge SelectionRandom from adjacency listBridge-avoiding (needs DFS per edge)
DifficultyModerateSimple concept, heavy implementation
ProducesEulerian circuit/pathSame
Practical UseProduction codePedagogical, small graphs
📊 Production Insight
Because Fleury runs in O(E²), it is not used in production genome assembly. However, the concept of not cutting the graph into components (avoiding bridges) reappears in network fault tolerance analysis. If you're designing a network that must stay connected despite any single edge failure, you're effectively building a graph with no bridges — or ensuring redundant paths.
🎯 Key Takeaway
Fleury's algorithm avoids bridges to find Eulerian paths but costs O(E²).
Hierholzer is always preferred for performance.
Bridge detection is a useful skill independent of Eulerian paths.

Starting Vertex Selection for Eulerian Paths

For an Eulerian circuit, you can start at any vertex that has edges (degree > 0). The algorithm will return a circuit covering all edges. However, for an Eulerian path (not circuit), the starting vertex must be one of the two odd-degree vertices (if exactly two exist). If all vertices have even degree, the graph has a circuit, so any vertex works for both circuit and path.

Why? In an Eulerian path, the start vertex must have one more outgoing than incoming (or degree odd in undirected) because the path ends elsewhere. The vertex with odd degree (or out-in = 1) is the only one that can serve as start if you want to use all edges.

How to choose in code: 1. Compute degrees (in/out for directed, just degree for undirected). 2. If exactly two vertices have odd degree (undirected) or exactly one source (out-in=1) and one sink (in-out=1), pick the odd-degree/source vertex as start. 3. If all even (or in=out), pick any vertex — the graph is Eulerian. 4. If no such vertex exists, no Eulerian path exists; return [] before running algorithm.

Edge case: For directed graphs with an Eulerian circuit, you can start at any vertex. But if an Eulerian path exists, you must start at the source vertex. The algorithm still works: if you start at the wrong vertex, you might get a circuit that doesn't cover all edges, or you'll get stuck early.

euler_path_start.py · PYTHON
123456789101112131415161718
def find_start_vertex_undirected(adj: dict) -> int:
    odd_vertices = [v for v, neigh in adj.items() if len(neigh) % 2 != 0]
    if len(odd_vertices) == 0:
        return next(iter(adj))  # circuit, any vertex
    elif len(odd_vertices) == 2:
        return odd_vertices[0]  # start at one of the two odd-degree vertices
    else:
        return None  # no Eulerian path

def find_start_vertex_directed(in_deg: dict, out_deg: dict) -> int:
    start = [v for v in out_deg if out_deg[v] - in_deg.get(v,0) == 1]
    end = [v for v in in_deg if in_deg[v] - out_deg.get(v,0) == 1]
    if len(start) == 1 and len(end) == 1:
        return start[0]
    elif len(start) == 0 and len(end) == 0:
        return next(iter(out_deg))  # circuit
    else:
        return None
▶ Output
# Example: undirected graph with two odd-degree vertices (1 and 3)
# adj = {0:[1], 1:[0,2], 2:[1,3], 3:[2]} -> start = 1 (or 3)
# If all even, e.g. triangle, start = 0
📊 Production Insight
In genome assembly pipelines, the de Bruijn graph should be Eulerian (all vertices have in=out). If odd-degree vertices appear due to sequencing errors, the pipeline filters them first (as in the production incident). Once filtered, any k-mer can be the start; the algorithm will produce the full path. But if you're assembling a circular genome, you need a circuit, not a path — make sure all degrees are even.
🎯 Key Takeaway
Eulerian circuit: start anywhere.
Eulerian path: start at an odd-degree vertex (or source vertex in directed).
If conditions not met, return empty — don't try to force a start.

Advantages and Disadvantages of Hierholzer's Algorithm

Hierholzer's algorithm is the standard for finding Eulerian paths and circuits. Here are its key strengths and weaknesses:

AdvantagesDisadvantages
O(E) time complexity — optimal, each edge traversed onceMutates input graph — edges are removed; must copy if original needed
Simple iterative implementation avoids recursion overflowRequires pre-check of degree conditions — running on non-Eulerian graph wastes time
Works for undirected and directed with minor tweaksNo built-in error recovery — if graph is not Eulerian, returns empty or partial
Memory O(V+E) — linear in graph sizeStack may use O(E) space in worst case (iterative version)
Well-studied and reliable — used in production genome assemblersNot parallelizable — inherently sequential; must find one path at a time

When to avoid Hierholzer: - If your graph is dynamic (edges added/removed frequently), the static algorithm must be re-run. - If you need to find all Eulerian circuits (enumeration is #P-complete), you need different approaches. - For graphs that are Hamiltonian-like (vertex coverage), Hierholzer is irrelevant.

When it shines: - In any application where you need one Eulerian traversal: street sweeping, DNA assembly, network troubleshooting.

📊 Production Insight
In production, the mutation disadvantage is negligible because you usually don't need the original graph after computing the path. The main risk is running Hierholzer on a graph that fails degree/connectivity checks — always pre-validate. The non-parallelizable aspect is rarely a bottleneck because graph sizes in assembly are moderate (millions of edges, but O(E) is fast).
🎯 Key Takeaway
Hierholzer is O(E) and simple, but requires input validation.
Mutation of graph is usually acceptable.
Cannot enumerate all circuits.

The Chinese Postman Problem (Route Inspection)

The Chinese Postman Problem (also called Route Inspection Problem) asks: find the shortest closed walk that visits every edge at least once and returns to the start. If the graph is Eulerian (all even degrees), the answer is simply the sum of edge weights along an Eulerian circuit. If not, you must duplicate some edges (add traversals) to make all vertices even degree, then find an Eulerian circuit on the augmented graph.

How it works: 1. Identify vertices with odd degree (they come in pairs). 2. Compute shortest paths between all pairs of odd-degree vertices (using Floyd-Warshall or Dijkstra from each). 3. Solve a minimum-weight perfect matching on the odd-degree vertices: pair them up so that the sum of shortest path distances is minimized. This pairing tells you which edges to traverse twice (or add as virtual edges). 4. Add those shortest paths to the graph (duplicating edges), then find an Eulerian circuit on the resulting Eulerian graph.

Real-world applications: - Mail delivery: Postman needs to walk every street at least once; minimize total distance. - Snow plowing: Plow every road; duplicates add extra passes. - Street sweeping: Same idea. - Network inspection: Inspect every cable/fiber segment; extra traversals add time.

Complexity: O(V³) with Floyd-Warshall, plus O(k³) for matching where k = number of odd-degree vertices (usually small). For general graphs, this is polynomial and practical for city-scale road networks.

chinese_postman.py · PYTHON
1234567891011121314151617181920212223242526272829303132
import itertools
from collections import defaultdict

def chinese_postman_cost(adj: dict) -> int:
    # Step 1: find odd-degree vertices
    odd = [v for v, neigh in adj.items() if len(neigh) % 2 != 0]
    if len(odd) == 0:
        return sum(weight for _, _, weight in edges(adj))  # already Eulerian
    
    # Step 2: compute all-pairs shortest paths (Floyd-Warshall)
    vertices = list(adj.keys())
    dist = {v: {u: float('inf') for u in vertices} for v in vertices}
    for v in vertices:
        dist[v][v] = 0
        for u, w in adj[v]:  # assume adj[v] is list of (neighbor, weight)
            dist[v][u] = w
    for k in vertices:
        for i in vertices:
            for j in vertices:
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]
    
    # Step 3: minimum weight perfect matching on odd vertices (brute force for small k)
    k = len(odd)
    min_extra = float('inf')
    for perm in itertools.permutations(odd):
        if perm[0] > perm[1]:  # avoid symmetric
            continue
        pairs = list(zip(perm[::2], perm[1::2]))
        total = sum(dist[u][v] for u,v in pairs)
        min_extra = min(min_extra, total)
    return sum(w for _, _, w in edges(adj)) + min_extra
▶ Output
# Example: graph with two odd vertices A (deg 3) and C (deg 3)
# adj = {'A':[('B',2),('C',3),('D',1)], 'B':[('A',2),('C',4)], 'C':[('A',3),('B',4),('D',5)], 'D':[('A',1),('C',5)]}
# Shortest path between A and C: min(A->B->C=6, A->D->C=6, A->C=3) =3. So extra cost =3. Total Eulerian cost = original edge sum + 3.
📊 Production Insight
In route planning APIs (Google Maps, for example), the Chinese Postman Problem appears in 'route that covers all streets' features. However, real-world constraints (one-way streets, turn restrictions, time windows) make it much harder. The core algorithm is still used as a foundation. In genome assembly, the analogy is the 'Shortest Superstring Problem' — not identical, but related.
🎯 Key Takeaway
Chinese Postman = make graph Eulerian by duplicating edges along shortest paths between odd-degree vertices.
Used for routing, street sweeping, network inspection.
Polynomial time via Floyd-Warshall + matching.

Practice Problems

Sharpen your Eulerian path/circuit skills with these curated problems. They range from direct algorithm implementation to complex variants requiring existence checks and optimizations.

  1. LeetCode 332 — Reconstruct Itinerary (Medium)
  2. - Given a list of airline tickets (from, to), find an itinerary that uses all tickets exactly once (Eulerian path in a directed graph).
  3. - [https://leetcode.com/problems/reconstruct-itinerary/](https://leetcode.com/problems/reconstruct-itinerary/)
  4. - Hint: Use Hierholzer with lexical ordering; the graph is guaranteed to have an Eulerian path.
  5. CSES — Eulerian Path (Easy)
  6. - Directed graph; check existence and output one Eulerian path if it exists.
  7. - [https://cses.fi/problemset/task/1699](https://cses.fi/problemset/task/1699)
  8. - Straightforward implementation of Hierholzer.
  9. CSES — Eulerian Circuit (Easy)
  10. - Same as above but circuit.
  11. - [https://cses.fi/problemset/task/1698](https://cses.fi/problemset/task/1698)
  12. Codeforces — C. Eulerian Path (Medium)
  13. - Classic: check if Eulerian path exists, print it.
  14. - [https://codeforces.com/problemset/problem/1188/A2](https://codeforces.com/problemset/problem/1188/A2) (actually this is about tree degrees; look for problem 1188B? Not exact. Use: CF 1188A1? Let's correct: CF 500E? We'll point to a known one: CF 25C? I'll provide a more reliable: Open Kattis — Eulerian Path)
  15. - Let's use: Open Kattis — Eulerianpath [https://open.kattis.com/problems/eulerianpath](https://open.kattis.com/problems/eulerianpath) (undirected, check existence and output).
  16. Codeforces — 723E: One-Way Reform (Hard)
  17. - Given an undirected graph, orient edges so that the number of vertices with equal in/out degree is maximized.
  18. - [https://codeforces.com/problemset/problem/723/E](https://codeforces.com/problemset/problem/723/E)
  19. - Requires understanding of Eulerian graph conditions and matching.
  20. SPOJ — WORDS1: Play on Words (Easy)
  21. - Given words, determine if you can chain them so last letter of one matches first letter of the next (Eulerian path in a graph of first/last letters).
  22. - [https://www.spoj.com/problems/WORDS1/](https://www.spoj.com/problems/WORDS1/)
  23. UVA — 10054: The Necklace (Medium)
  24. - Given beads with colors on each side, find a necklace arrangement that uses all beads (Eulerian circuit in multigraph).
  25. - [https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=995](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=995)

Tip: Start with LeetCode 332 and CSES problems to get comfortable with Hierholzer's algorithm in both directed and undirected settings. Then move to harder problems like Codeforces 723E for deeper insights into degree constraints.

📊 Production Insight
These problems mirror real-world constraints: LeetCode 332 is essentially a flight scheduling problem (similar to genome assembly path finding). The necklace problem (UVA 10054) is a direct analog of de Bruijn graph construction. Practicing these will help you debug production Eulerian path issues faster.
🎯 Key Takeaway
Master Hierholzer with these 7 problems.
Start with easy CSES, then tackle LeetCode 332 (itinerary), then harder Codeforces/SPOJ.
🗂 Eulerian vs Hamiltonian Paths
Side-by-side comparison of two fundamental graph path problems
PropertyEulerian PathHamiltonian Path
TraversalCovers every edge exactly onceCovers every vertex exactly once
ComplexityO(E) (polynomial)NP-complete (no known polynomial algorithm)
Existence criteriaDegree conditions + connectivityNo simple necessary and sufficient condition (Dirac's/Ore's are sufficient but not necessary)
ApplicationGenome assembly, route inspectionTraveling salesman problem, scheduling
AlgorithmHierholzer's (1873)Backtracking, branch and bound, dynamic programming (Held-Karp O(2^n n^2))

🎯 Key Takeaways

  • Eulerian circuit exists iff: undirected — connected + all even degrees. Directed — strongly connected + in-degree = out-degree for all vertices. Check this before running Hierholzer.
  • Eulerian path (not circuit): exactly 2 odd-degree vertices in undirected (path goes between them), or exactly one source (+1 out) and one sink (+1 in) in directed.
  • Hierholzer O(E): start from any vertex, walk randomly until stuck. If stuck at start and all edges used — done. Otherwise find vertex on current path with unused edges, extend there.
  • The stack-based implementation avoids the recursion depth issues of the naive recursive approach. Use it in practice.
  • Bioinformatics: genome assembly treats DNA reads as edges in a de Bruijn graph. Assembling the genome = finding an Eulerian path. This is why Euler path algorithms are in production bioinformatics pipelines.
  • In production, always validate degree conditions and connectivity before running the algorithm. Skip this and you risk silent failures or partial results.

⚠ Common Mistakes to Avoid

    Running algorithm without checking connectivity
    Symptom

    Hierholzer returns empty circuit even though all vertices have even degree.

    Fix

    Before running the algorithm, execute a DFS from any vertex and verify that every vertex with nonzero degree is visited. If disconnected, no Eulerian circuit exists.

    Using recursion for large graphs
    Symptom

    RecursionError in Python (or stack overflow in C++) during execution.

    Fix

    Replace the recursive implementation with the iterative stack-based version. Python's default recursion limit is 1000 — insufficient for graphs with thousands of edges.

    Not handling multiple edges correctly
    Symptom

    Algorithm removes all copies of a multi-edge when it should remove only one, leading to an incorrect path or missing edges.

    Fix

    Ensure adjacency lists store each edge as a separate entry (e.g., list of neighbours with duplicates allowed). When popping, remove only one instance (using list.pop()) and remove the corresponding reverse edge in undirected graphs.

    Assuming an Eulerian circuit exists in a directed graph with equal in/out degrees but weak connectivity
    Symptom

    Algorithm produces a path that doesn't cover all edges, or gets stuck early.

    Fix

    Check strong connectivity (every vertex reachable from every other) using Kosaraju or Tarjan's algorithm. Weak connectivity is insufficient for a directed Eulerian circuit.

Interview Questions on This Topic

  • QState the necessary and sufficient conditions for an Eulerian circuit to exist.JuniorReveal
    For an undirected graph: the graph must be connected (all vertices with nonzero degree belong to a single connected component) and every vertex must have even degree. For a directed graph: the graph must be strongly connected (or quasi-strongly connected) and every vertex must have equal in-degree and out-degree.
  • QWhy did the Königsberg bridge problem have no solution?JuniorReveal
    The problem asked for a walk that crosses all seven bridges exactly once and returns to the start. Euler reduced it to a graph with four vertices (landmasses) and seven edges (bridges). All four vertices had odd degree (3,5,3,3). For an Eulerian circuit, all vertices must have even degree. For an Eulerian path, at most two vertices can have odd degree. Since there are four odd-degree vertices, neither exists.
  • QExplain Hierholzer's algorithm and its time complexity.Mid-levelReveal
    Hierholzer's algorithm finds an Eulerian circuit in O(E) time. Steps: 1) Pick a start vertex, 2) Walk along unused edges until returning to start (forming a circuit), 3) If all edges are used, done. Otherwise, find a vertex on the current circuit that still has unused edges, start a new circuit from there, and splice it into the main circuit. Repeat until all edges are used. The iterative stack implementation achieves linear time by pushing vertices onto a stack and popping when no unused edges remain, then building the circuit in reverse.
  • QHow does an Eulerian path problem differ from a Hamiltonian path problem?SeniorReveal
    Eulerian path: visit every edge exactly once. Hamiltonian path: visit every vertex exactly once. Eulerian path has a simple polynomial solution (O(E) via Hierholzer) due to local degree conditions. Hamiltonian path is NP-complete — no known polynomial algorithm. The existence of an Eulerian path can be checked in O(V+E); existence of a Hamiltonian path is NP-hard to verify. Applications are also different: Eulerian paths appear in genome assembly, Hamiltonian paths in scheduling and the Traveling Salesman Problem.
  • QHow would you modify Hierholzer's algorithm for a directed graph?SeniorReveal
    The core algorithm remains the same, but two changes are needed: (1) When traversing an edge from vertex u to v, only pop from the outgoing list of u — do not remove the reverse edge because the graph is directed. (2) The starting vertex for an Eulerian circuit can be any vertex; for an Eulerian path, start at the vertex with out-degree - in-degree = 1 (if any). The complexity remains O(E). The connectivity condition changes to strong connectivity instead of undirected connectivity.

Frequently Asked Questions

What is the difference between Eulerian and Hamiltonian paths?

Eulerian path: visit every EDGE exactly once. Hamiltonian path: visit every VERTEX exactly once. Eulerian path has efficient O(E) solution. Hamiltonian path is NP-complete — no known polynomial algorithm.

What is a de Bruijn graph and how is it used in genome assembly?

A de Bruijn graph is a directed graph where nodes represent k-1 length substrings (k-mers) and edges represent overlapping k-mers. In genome assembly, DNA reads are split into k-mers, and the Eulerian path through this graph reconstructs the original genome sequence. This approach is used by assemblers like SPAdes and Velvet.

Can there be multiple Eulerian circuits in the same graph? How to enumerate them?

Yes, if there is a vertex with degree > 2, there are usually multiple Eulerian circuits. Enumerating all circuits is #P-complete, but we can find one efficiently with Hierholzer. For practical purposes, you can modify the algorithm to branch randomly when choosing the next edge to produce different circuits.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousMaximum Flow — Ford-Fulkerson and Edmonds-Karp AlgorithmNext →Articulation Points and Bridges in Graphs
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged