Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 11 – Graphs: modelling, traversal and connectivity

Euler paths and circuits

When a walk can use every edge of a graph exactly once, the degree test that decides it, and how Hierholzer's algorithm builds the walk in linear time.

  • Advanced
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 8 of 25 minutes free
Free: Every street once: the degree test · Hierholzer's algorithm, step by step · Euler or Hamiltonian · Exercise and self-check. With a pass: 3 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • State the degree conditions for Euler paths and circuits
  • Construct an Euler circuit with Hierholzer's algorithm
  • Distinguish Euler (every edge) from Hamiltonian (every vertex) problems

Before you start

Euler paths and circuits answer one question about a graph: can a single walk use every edge exactly once? An Euler path may end anywhere; an Euler circuit must end where it began. In an undirected graph whose edges form one connected piece, an Euler circuit exists exactly when every vertex has even degree, and an Euler path exactly when zero or two vertices have odd degree; with two, the path runs from one odd vertex to the other. In a directed graph every vertex needs as many edges in as out for a circuit, and for a path one start vertex may have one extra edge out and one end vertex one extra edge in. Hierholzer’s algorithm builds Euler circuits and paths in O(E) time. Visiting every vertex once instead, the Hamiltonian problem, sounds similar and is NP-complete.

Every street once: the degree test

A road-inspection truck must drive every street of a small map exactly once. Map B has seven junctions and nine two-way streets:

Seven junctions joined by nine streets; junctions 0 and 4 have odd degree, every other junction even.0: degree 3, odd1: degree 22: degree 43: degree 24: degree 3, odd5: degree 26: degree 2

Map B, with the degree of every junction

Text description of the diagram

Seven junctions, numbered 0 to 6, are joined by nine two-way streets: 0–1, 1–2, 2–3, 3–0, 0–2, 2–4, 4–5, 5–6 and 6–4.

Each junction is labelled with its degree, the number of streets that meet there. Junction 0 has degree 3, junction 1 has 2, junction 2 has 4, junction 3 has 2, junction 4 has 3, and junctions 5 and 6 have 2 each.

Only junctions 0 and 4 have odd degree, and they are marked. A route that drives every street exactly once must start at one of them and end at the other. Map A, which the lesson also tests, has one more street, from 1 to 3; it makes junctions 1 and 3 odd as well, and with four odd junctions no such route exists.

Each time a route passes through a junction, it uses two of its streets, one to arrive and one to leave. Only where the route starts and where it ends is a single street used without a partner. So every junction where the route neither starts nor ends must have even degree, and a route that drives every street once can exist only if zero or two junctions have odd degree. Map B has two, junctions 0 and 4, so its route must run between them. Map A, the same map with one more street from 1 to 3, has four odd junctions, so no such route exists, however it is searched for.

The degree test needs one more condition: all the streets must lie in one connected piece. A junction with no streets does not matter, but two separate groups of streets can never be driven in one walk, even when every degree is even. When the test fails, the shortest route that drives every street at least once, the Chinese postman route, repeats a few streets.

Hierholzer’s algorithm, step by step

Start at an odd junction, or anywhere when every degree is even, and keep a stack of junctions.

  1. Look at the junction on top of the stack. If it still has an unused street, mark the street used, and push the junction at its other end.
  2. If it has no unused street left, pop it and add it to the route.
  3. Repeat until the stack is empty, then reverse the route.

A walk that gets stuck before every street is used is not a failure. The stack still holds the junctions of the walk, and the first one with an unused street starts a new loop, which the route takes in at that junction:

The degree test on two maps, then Hierholzer's walks on map B Python · trace/inspection.py
# A road-inspection truck must drive every street of a small map exactly once. First the degree test on two versions
# of the map, then Hierholzer's algorithm builds the route with an explicit stack: walk along unused streets until
# stuck, and add a junction to the route only when it has no unused street left. The trace prints each walk.

def degrees(n, streets):
    deg = [0] * n
    for u, v in streets:
        deg[u] += 1
        deg[v] += 1
    return deg


def inspection_route(n, streets, trace=False):
    """A route that uses every street once, as a list of junctions, or None."""
    adj = [[] for _ in range(n)]
    for i, (u, v) in enumerate(streets):
        adj[u].append((v, i))
        adj[v].append((u, i))
    odd = [v for v in range(n) if len(adj[v]) % 2 == 1]
    if len(odd) not in (0, 2):
        return None
    start = odd[0] if odd else next(v for v in range(n) if adj[v])
    used, nxt = [False] * len(streets), [0] * n      # nxt[v]: the first street of v that may still be unused
    stack, route, walk = [start], [], [start]
    while stack:
        v = stack[-1]
        while nxt[v] < len(adj[v]) and used[adj[v][nxt[v]][1]]:
            nxt[v] += 1
        if nxt[v] == len(adj[v]):
            if trace and len(walk) > 1:
                print(f"  walk {' '.join(map(str, walk))}: stuck at {walk[-1]}")
            walk = []
            route.append(stack.pop())                 # stuck: v is final, from the end of the route backwards
        else:
            if trace and not walk:
                print(f"  back up to {v}, which still has an unused street")
                walk = [v]
            w, i = adj[v][nxt[v]]
            used[i] = True
            stack.append(w)
            walk.append(w)
    if len(route) != len(streets) + 1:
        return None                                   # some streets lie in another piece of the map
    return route[::-1]


MAP_A = [(0, 1), (1, 2), (2, 4), (4, 5), (5, 6), (6, 4), (2, 3), (3, 0), (0, 2), (1, 3)]
MAP_B = MAP_A[:-1]                                    # the same map without the street from 1 to 3
for name, streets in [("map A", MAP_A), ("map B", MAP_B)]:
    deg = degrees(7, streets)
    odd = [v for v in range(7) if deg[v] % 2]
    print(f"{name}: {len(streets)} streets, degrees {deg}, odd junctions {odd}")
print("map B, Hierholzer's walks:")
route = inspection_route(7, MAP_B, trace=True)
print("map B route:", " -> ".join(map(str, route)))
driven = sorted(tuple(sorted(p)) for p in zip(route, route[1:]))
print("every street exactly once:", driven == sorted(tuple(sorted(s)) for s in MAP_B))
print("map A route:", inspection_route(7, MAP_A))

Output

map A: 10 streets, degrees [3, 3, 4, 3, 3, 2, 2], odd junctions [0, 1, 3, 4]
map B: 9 streets, degrees [3, 2, 4, 2, 3, 2, 2], odd junctions [0, 4]
map B, Hierholzer's walks:
  walk 0 1 2 4 5 6 4: stuck at 4
  back up to 2, which still has an unused street
  walk 2 3 0 2: stuck at 2
map B route: 0 -> 1 -> 2 -> 3 -> 0 -> 2 -> 4 -> 5 -> 6 -> 4
every street exactly once: True
map A route: None

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 inspection.py

The first walk, 0 1 2 4 5 6 4, ends at junction 4 with three streets still unused. Backing up the stack, junction 2 still has the street to 3, so a second walk, 2 3 0 2, starts there and closes a loop. The finished route, read backwards from the pops, drives the loop when it reaches 2: 0, 1, 2, 3, 0, 2, 4, 5, 6, 4. Each street is pushed once and each junction popped once per visit, so the work is O(V + E).

Euler or Hamiltonian

An Euler path against a Hamiltonian path
Criterion Euler pathHamiltonian path
Uses exactly once Every edgeEvery vertex
Deciding whether one exists The degree test and one connectivity check: O(V + E)NP-complete: no polynomial-time method is known
Finding one Hierholzer's algorithm: O(E)Backtracking or dynamic programming over subsets: exponential
Typical question Drive every street, draw a figure without lifting the penVisit every city once, a tour of every stop
When to choose Edges are the things to coverVertices are the things to cover

The two look alike in a problem statement, so read which one it asks for. Covering every vertex is the travelling salesman’s world, where Karp’s 1972 list of NP-complete problems puts the Hamiltonian circuit, while covering every edge needs only degrees and one search.

Euler paths and circuits in a graph with V vertices and E edges
Operation Time Extra space
Degree test O(V + E) O(V)
Connectivity of the edges O(V + E) O(V)
Hierholzer's algorithm O(V + E) O(V + E): the stack and the used marks

Exercise and self-check

Return a path through every edge of a directed graph, or report that none exists, up to a cycle of 100,000 edges.

Exercise · Hard · Python, JavaScript

An Euler path through a directed graph

A directed graph has n vertices, numbered from 0, and at least one directed edge (u, v) (JavaScript: two-element arrays). Edges may repeat, and an edge may run from a vertex to itself. Write euler_path(n, edges) (JavaScript: eulerPath) that returns a path through every edge exactly once, as the list of the vertices it passes, len(edges) + 1 of them, or an empty list when no such path exists.

Any valid path is accepted: the tests check that consecutive vertices use every edge exactly once. They cover the examples of the lesson, graphs that fail the degree test, graphs that pass it but come in two pieces, loops and repeated edges, random graphs that surely have a path, and a single cycle of 100,000 edges, which needs a loop rather than one recursive call per edge.

Python · Starter code · euler.py

def euler_path(n, edges):
    """The vertices of a path that uses every edge exactly once, or [] if there is none."""
    raise NotImplementedError("write euler_path")
The sample tests · test_euler.py
import random
from collections import Counter

from euler import euler_path


def is_euler_path(path, edges):
    return len(path) == len(edges) + 1 and Counter(zip(path, path[1:])) == Counter(map(tuple, edges))


def test_lesson_example():
    """six edges with one start and one end"""
    edges = [(0, 1), (1, 2), (2, 0), (0, 3), (3, 1), (1, 4)]
    path = euler_path(5, edges)
    assert is_euler_path(path, edges), path
    assert path[0] == 0 and path[-1] == 4


def test_degree_test_fails():
    """two edges out of one vertex, and two separate starts"""
    assert euler_path(3, [(0, 1), (0, 2)]) == []
    assert euler_path(4, [(0, 1), (2, 3)]) == []


def test_two_pieces():
    """every degree balanced, but the edges form two loops that never meet"""
    assert euler_path(6, [(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3)]) == []


def test_loops_and_repeats():
    """a loop at a vertex and an edge given twice"""
    for edges in ([(0, 0)], [(0, 1), (1, 0), (0, 1), (1, 0)], [(2, 2), (2, 0), (0, 2)], [(1, 1), (1, 2), (2, 1), (1, 1)]):
        path = euler_path(3, edges)
        assert is_euler_path(path, edges), (edges, path)


def test_random_walks():
    """200 random walks: their edges, shuffled, always have an Euler path"""
    rng = random.Random(23)
    for _ in range(200):
        n = rng.randint(1, 6)
        walk = [rng.randrange(n) for _ in range(rng.randint(2, 12))]
        edges = list(zip(walk, walk[1:]))
        rng.shuffle(edges)
        assert is_euler_path(euler_path(n, edges), edges), (n, edges)


def test_long_cycle():
    """a single cycle of 100,000 edges"""
    n = 100_000
    edges = [(i, (i + 1) % n) for i in range(n)]
    path = euler_path(n, edges)
    assert is_euler_path(path, edges)

JavaScript · Starter code · euler.mjs

/** The vertices of a path that uses every edge exactly once, or [] if there is none. */
export function eulerPath(n, edges) {
  throw new Error('write eulerPath');
}
The sample tests · euler.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { eulerPath } from './euler.mjs';

/** Whether consecutive vertices of the path use every edge exactly once. */
function isEulerPath(path, edges) {
  if (!Array.isArray(path) || path.length !== edges.length + 1) return false;
  const count = new Map();
  for (const [u, v] of edges) count.set(`${u},${v}`, (count.get(`${u},${v}`) ?? 0) + 1);
  for (let i = 0; i + 1 < path.length; i++) {
    const key = `${path[i]},${path[i + 1]}`;
    const left = count.get(key) ?? 0;
    if (left === 0) return false;
    count.set(key, left - 1);
  }
  return true;
}

test('six edges with one start and one end', () => {
  const edges = [[0, 1], [1, 2], [2, 0], [0, 3], [3, 1], [1, 4]];
  const path = eulerPath(5, edges);
  assert.ok(isEulerPath(path, edges), JSON.stringify(path));
  assert.equal(path[0], 0);
  assert.equal(path[path.length - 1], 4);
});

test('two edges out of one vertex, and two separate starts', () => {
  assert.deepEqual(eulerPath(3, [[0, 1], [0, 2]]), []);
  assert.deepEqual(eulerPath(4, [[0, 1], [2, 3]]), []);
});

test('every degree balanced, but the edges form two loops that never meet', () => {
  assert.deepEqual(eulerPath(6, [[0, 1], [1, 2], [2, 0], [3, 4], [4, 5], [5, 3]]), []);
});

test('a loop at a vertex and an edge given twice', () => {
  for (const edges of [[[0, 0]], [[0, 1], [1, 0], [0, 1], [1, 0]], [[2, 2], [2, 0], [0, 2]], [[1, 1], [1, 2], [2, 1], [1, 1]]]) {
    const path = eulerPath(3, edges);
    assert.ok(isEulerPath(path, edges), JSON.stringify([edges, path]));
  }
});

test('200 random walks: their edges, shuffled, always have an Euler path', () => {
  let seed = 23;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  const pick = (k) => Math.floor(random() * k);
  for (let t = 0; t < 200; t++) {
    const n = 1 + pick(6);
    const walk = Array.from({ length: 2 + pick(11) }, () => pick(n));
    const edges = walk.slice(1).map((v, i) => [walk[i], v]);
    for (let i = edges.length - 1; i > 0; i--) {
      const j = pick(i + 1);
      [edges[i], edges[j]] = [edges[j], edges[i]];
    }
    assert.ok(isEulerPath(eulerPath(n, edges), edges), JSON.stringify([n, edges]));
  }
});

test('a single cycle of 100,000 edges', () => {
  const n = 100000;
  const edges = [];
  for (let i = 0; i < n; i++) edges.push([i, (i + 1) % n]);
  assert.ok(isEulerPath(eulerPath(n, edges), edges));
});
A hint

Count out - in for every vertex first: a path exists only if every vertex has 0, except at most one with +1, where the path must start, and one with -1, where it ends. Then run Hierholzer's algorithm with an explicit stack: follow an unused edge from the top vertex while there is one, and move the top vertex to the answer when it has none left. If the answer is shorter than len(edges) + 1, some edges were in another piece.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 A connected undirected graph has the degrees 2, 4, 3, 2, 3 and 2. What does the degree test say?

    Choose one answer.

    Show the answer to question 1

    Answer: An Euler path exists, between the two vertices of degree 3, but no Euler circuit

    Exactly two vertices have odd degree, so a walk through every edge exists and must start at one of them and end at the other. A circuit would need every degree even.

  2. Question 2 of 4 Hierholzer's algorithm runs on a connected graph with 37 edges that has an Euler path. How many vertices does the finished route list, repeats included?

    Type a number.

    Show the answer to question 2

    Answer: 38 vertices

    A walk through 37 edges passes 37 + 1 = 38 vertices. Checking that number at the end is how the code notices edges that lay in another piece of the graph.

  3. Question 3 of 4 In a directed graph, which degrees allow an Euler path that is not a circuit?

    Choose one answer.

    Show the answer to question 3

    Answer: One vertex with one more edge out than in, one with one more in than out, and every other vertex balanced

    The path starts at the vertex with the extra edge out and ends at the vertex with the extra edge in. Adding in and out degrees, as for an undirected graph, loses the direction and accepts graphs with no path at all.

  4. Question 4 of 4 A delivery van must stop at each of 40 shops exactly once. Which kind of problem is that?

    Choose one answer.

    Show the answer to question 4

    Answer: A Hamiltonian path problem, NP-complete in general

    Each shop is a vertex to visit once, not an edge to drive once. Euler problems cover every edge and are easy; Hamiltonian problems cover every vertex and have no known polynomial-time algorithm.

Why the degree test is enough, proved, and why the walk must back up Pro: opens with a Pro, Premium or Ultimate pass

The degree test is easy to state, and the reason it is enough is the heart of Hierholzer’s algorithm. This part proves both directions of the test, proves that the stack never strands an edge, and reproduces the walk that gets stuck without it.

In this part:

  • The degree conditions are necessary and sufficient: the proof
  • Hierholzer’s stack never strands an edge: the invariant
  • A walk that gets stuck, reproduced

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

The alphabetically first directed path in five languages, and texts rebuilt from fragments Pro: opens with a Pro, Premium or Ultimate pass

Directed graphs change the test from even degrees to balanced ones, and they power one striking use: rebuilding a long text from short overlapping pieces. This part runs the directed algorithm in five languages and rebuilds a text from its 3-letter fragments.

In this part:

  • The alphabetically first directed path, in five languages
  • Rebuilding a text from its 3-letter fragments

4 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Four bugs reproduced, the shortest route that repeats streets, and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Euler code fails in quiet ways: it returns something that looks like a route. This part reproduces four such bugs, solves the case where no Euler path exists and some streets must be driven twice, and ends with a practice set.

In this part:

  • Four bugs, each on the smallest input that shows it
  • When no Euler path exists: the shortest route that repeats streets
  • A practice set, with worked answers

2 runnable examples (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    One route must drive every street of a town exactly once, another must stop at every junction exactly once. Which is the Euler problem, and why is the other much harder?

    Driving every street once is the Euler problem: the streets are the edges to use exactly once. It is decided by degrees alone, every junction even, or exactly two odd ones where the route starts and ends, plus a check that the streets form one connected piece, and Hierholzer's algorithm builds the route in O(E) time. Stopping at every junction once is the Hamiltonian problem: the junctions are the vertices to visit once. No local test like the degrees decides it, and it is NP-complete, so no polynomial-time algorithm is known; exact solutions need backtracking or dynamic programming over subsets of junctions, which grows exponentially. The two statements differ in one word, edge or vertex, so read which one is asked.

  2. Mid to senior

    A city wants a street-sweeping route that drives each of its 3,800 street segments and returns to the depot, with as little driving twice as possible. How do you decide whether a route without repeats exists, build it, and plan the repeats when it does not?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Senior to staff

    You receive 50,000 overlapping 8-character fragments of an unknown text, each 8-character window exactly once and in no order. How do you rebuild the text, how fast is it, and when can you not be sure that your answer is the original?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A recursive Hierholzer passes every unit test but crashes on a graph with 300,000 edges. A colleague's loop-based rewrite no longer crashes, but the route validator rejects its output on some inputs. Explain both failures and what the correct loop must do.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Euler paths and circuits use every edge exactly once; Hamiltonian ones visit every vertex once, a far harder problem.
  • Undirected: a circuit needs every degree even, a path zero or two odd degrees, with the edges in one piece.
  • Directed: in-degree equals out-degree everywhere, except one start and one end that differ by one.
  • Hierholzer’s algorithm walks until stuck, pops vertices into the route and splices loops in, in O(E).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.