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 9 – Trees and binary search trees

Binary tree traversals: DFS orders and BFS

Preorder, inorder, postorder and level order on one tree: the rule behind each, recursive code with recorded output, what each costs and when to use it.

  • Beginner
  • 30 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: 10 of 30 minutes free
Free: Four orders on one tree · The code, and what it costs · Telling the orders apart · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Implement preorder, inorder and postorder traversals recursively and iteratively
  • Traverse level by level with a queue
  • Identify which traversal produced a given output

Before you start

To traverse a binary tree is to visit every node exactly once, in an order fixed in advance. The depth-first orders follow one branch to the bottom before backing up, and they differ only in when they visit a node: preorder visits it before its two subtrees, inorder between them and postorder after both. Level order is the breadth-first order: it takes the tree one level at a time, from the root down and from left to right, with a queue. Each of the four takes O(n) time for n nodes. A recursive depth-first walk keeps one call open for each node on the path it is on, O(h) memory for a tree of height h, while level order holds up to a whole level in its queue. The order decides what a program can do: copy a tree, list a search tree’s keys in sorted order, add up sizes from the leaves, or reach the nodes closest to the root first.

Four orders on one tree

A binary tree of nine keys with M at the root, E and S below it; H has only a left child, G, and P only a right child, R.WRPGHBSEMrightrightleftleftrightleftrightleft

The tree whose four orders this lesson compares

Text description of the diagram

The diagram shows a binary tree of nine nodes. Every edge is labelled left or right.

  • M is the root. Its left child is E and its right child is S.
  • E has a left child, B, which is a leaf, and a right child, H. H has only a left child, G, which is a leaf.
  • S has a left child, P, and a right child, W, which is a leaf. P has only a right child, R, which is a leaf.

Its four traversal orders are: preorder M E B H G S P R W; inorder B E G H M P R S W; postorder B G H E R P W S M; level order M E S B H P W G R. The keys are also in alphabetical order from left to right, so the tree is a binary search tree and its inorder sequence is sorted.

Each depth-first order is one rule applied at every node, so a single line of the rule produces the whole sequence:

Order Rule at every node
Preorder node, then left, then right
Inorder left, then node, then right
Postorder left, then right, then node
Level order one level at a time, left to right

On this tree, and where each order is used:

  • Preorder: M E B H G S P R W. Copying a tree, writing it out to rebuild later, and prefix notation.
  • Inorder: B E G H M P R S W. A binary search tree’s keys in sorted order.
  • Postorder: B G H E R P W S M. Sizes and heights, deleting a tree, and evaluating an expression.
  • Level order: M E S B H P W G R. The nodes nearest the root first, and printing by levels.

The code, and what it costs

The three recursive functions below differ only in where the line that records the node sits; preorder also counts how many calls are open at once. Level order takes nodes from the front of a collections.deque and adds their children at the back, which keeps every level ahead of the next. The same program then stores (7 − 2) × (3 + 1) as a tree and walks it:

Four orders of one tree, and an expression tree walked three ways Python · orders/traversals.py
"""The four traversal orders of one binary tree, and what each order is good for."""
from collections import deque


class Node:
    def __init__(self, key, left=None, right=None):
        self.key, self.left, self.right = key, left, right


# The lesson's tree: M at the root, E and S below it, and so on down to G and R.
tree = Node("M",
            Node("E", Node("B"), Node("H", Node("G"))),
            Node("S", Node("P", None, Node("R")), Node("W")))

calls = {"open": 0, "most": 0}


def preorder(node, out):
    calls["open"] += 1
    calls["most"] = max(calls["most"], calls["open"])
    if node is not None:
        out.append(node.key)          # visit before both subtrees
        preorder(node.left, out)
        preorder(node.right, out)
    calls["open"] -= 1
    return out


def inorder(node, out):
    if node is not None:
        inorder(node.left, out)
        out.append(node.key)          # visit between the subtrees
        inorder(node.right, out)
    return out


def postorder(node, out):
    if node is not None:
        postorder(node.left, out)
        postorder(node.right, out)
        out.append(node.key)          # visit after both subtrees
    return out


def level_order(root):
    out, queue, widest = [], deque([root]), 1
    while queue:
        widest = max(widest, len(queue))
        node = queue.popleft()
        out.append(node.key)
        queue.extend(c for c in (node.left, node.right) if c is not None)
    return out, widest


print("preorder   ", " ".join(preorder(tree, [])))
print("inorder    ", " ".join(inorder(tree, [])))
print("postorder  ", " ".join(postorder(tree, [])))
order, widest = level_order(tree)
print("level order", " ".join(order))
print(f"most calls open at once: {calls['most']} (height 3, plus the root's call and one on an empty child)")
print(f"largest queue: {widest} nodes")

# An expression tree: (7 - 2) * (3 + 1). The three depth-first orders spell its three notations.
expr = Node("*", Node("-", Node("7"), Node("2")), Node("+", Node("3"), Node("1")))
print("prefix  (preorder): ", " ".join(preorder(expr, [])))
print("infix   (inorder):  ", " ".join(inorder(expr, [])), " <- the brackets are lost")
print("postfix (postorder):", " ".join(postorder(expr, [])))


def evaluate(node):
    """Postorder: both operands are ready before their operator."""
    if node.left is None:
        return int(node.key)
    a, b = evaluate(node.left), evaluate(node.right)
    return {"+": a + b, "-": a - b, "*": a * b}[node.key]


print("value:", evaluate(expr))

Output

preorder    M E B H G S P R W
inorder     B E G H M P R S W
postorder   B G H E R P W S M
level order M E S B H P W G R
most calls open at once: 5 (height 3, plus the root's call and one on an empty child)
largest queue: 4 nodes
prefix  (preorder):  * - 7 2 + 3 1
infix   (inorder):   7 - 2 * 3 + 1  <- the brackets are lost
postfix (postorder): 7 2 - 3 1 + *
value: 20

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

Every node is entered once and every empty child is checked once, so each order runs in O(n) time. The memory is where they differ. A recursive walk keeps a call open for every node between the root and its current position, plus one for an empty child: at most 5 calls for this tree of height 3, O(h) in general, which is about log2 n for a balanced tree and n for a path. Level order keeps whole levels in its queue: 4 nodes at most here, and about half of all the nodes for a perfect tree, whose last level holds that many.

The expression tree shows why the choice matters. Preorder writes each operator before its operands (prefix notation) and postorder writes it after them, the postfix form that a stack evaluates. evaluate works in postorder for the same reason, since an operator needs both operands first, and returns 20. Inorder gives the familiar infix form, but without brackets it reads 7 − 2 × 3 + 1, which is a different expression, so a program that prints expressions in inorder has to put brackets around every subtree.

Telling the orders apart

Three clues identify most sequences. Preorder and level order always start with the root, and postorder always ends with it. Inorder lists a binary search tree’s keys in increasing order, as it does for this tree’s alphabetical keys. The leaves appear in the same left-to-right order in all three depth-first orders (B, G, R, W here), because each of them finishes a node’s left subtree before it starts the right one.

Other ways to walk the same orders

The recursive functions are short, and each alternative removes one of their weaknesses. An explicit stack takes over from the call stack, so a path of 100,000 nodes can no longer overflow it. Morris traversal produces inorder with O(1) extra memory by linking nodes to their in-order successors for a moment. Reading the queue’s length before each level turns level order into per-level answers, such as zig-zag order or the last node of each level.

Exercise: levels and zig-zag order

The exercise asks for the keys of each level, then for zig-zag order. Its tests include a path of 1,200 nodes, more than CPython’s 1,000 nested calls, so it needs the queue rather than recursion.

Exercise · Easy · Python, JavaScript

Level order by levels, and in zig-zag

Node holds a key and two children, left and right, either of which may be None (JavaScript: null). Write two functions:

  • levels(root): the keys of the tree level by level, as a list with one list per level, from the root down, each level read from left to right. An empty tree (root is None) gives [].
  • zigzag(root): the same lists, except that every second level, the 2nd, the 4th and so on, is read from right to left.

For the lesson's tree, levels returns [["M"], ["E", "S"], ["B", "H", "P", "W"], ["G", "R"]] and zigzag returns [["M"], ["S", "E"], ["B", "H", "P", "W"], ["R", "G"]]. The tests also use a tree with gaps in its levels, a perfect tree of 2,047 nodes and a path of 1,200 nodes, which is deeper than Python's default limit of 1,000 nested calls, so a solution that recurses once per level fails there.

Python · Starter code · tree_levels.py

class Node:
    def __init__(self, key, left=None, right=None):
        self.key, self.left, self.right = key, left, right


def levels(root):
    """The keys of each level, from the root down, each level left to right ([] for an empty tree)."""
    raise NotImplementedError("write levels")


def zigzag(root):
    """Like levels, but the 2nd, 4th, ... levels are read right to left."""
    raise NotImplementedError("write zigzag")
The sample tests · test_tree_levels.py
from tree_levels import Node, levels, zigzag


def lesson_tree():
    return Node("M", Node("E", Node("B"), Node("H", Node("G"))), Node("S", Node("P", None, Node("R")), Node("W")))


def perfect(height, start=1):
    """A perfect tree whose keys are numbered 1, 2, 3, ... in level order."""
    if height < 0:
        return None
    return Node(start, perfect(height - 1, 2 * start), perfect(height - 1, 2 * start + 1))


def test_empty_tree():
    """an empty tree has no levels"""
    assert levels(None) == []
    assert zigzag(None) == []


def test_single_node():
    """a single node is one level"""
    assert levels(Node(7)) == [[7]]
    assert zigzag(Node(7)) == [[7]]


def test_lesson_tree():
    """the lesson's tree, by levels and in zig-zag"""
    assert levels(lesson_tree()) == [["M"], ["E", "S"], ["B", "H", "P", "W"], ["G", "R"]]
    assert zigzag(lesson_tree()) == [["M"], ["S", "E"], ["B", "H", "P", "W"], ["R", "G"]]


def test_gaps_in_levels():
    """missing children leave gaps, and the levels close up around them"""
    t = Node(1, Node(2, None, Node(4, Node(6))), Node(3, None, Node(5, None, Node(7))))
    assert levels(t) == [[1], [2, 3], [4, 5], [6, 7]]
    assert zigzag(t) == [[1], [3, 2], [4, 5], [7, 6]]


def test_perfect_tree():
    """a perfect tree of height 10: level d holds the keys 2^d to 2^(d+1) - 1"""
    got = levels(perfect(10))
    assert got == [list(range(2 ** d, 2 ** (d + 1))) for d in range(11)]
    assert zigzag(perfect(10))[9] == list(range(2 ** 10 - 1, 2 ** 9 - 1, -1))


def test_long_path():
    """a path of 1,200 nodes, deeper than Python's default recursion limit, has 1,200 levels of one node"""
    root = node = Node(0)
    for k in range(1, 1_200):
        node.right = Node(k)
        node = node.right
    got = levels(root)
    assert len(got) == 1_200 and got[-1] == [1_199]
    assert zigzag(root)[777] == [777]

JavaScript · Starter code · tree_levels.mjs

export class Node {
  constructor(key, left = null, right = null) {
    this.key = key;
    this.left = left;
    this.right = right;
  }
}

/** The keys of each level, from the root down, each level left to right ([] for an empty tree). */
export function levels(root) {
  throw new Error('write levels');
}

/** Like levels, but the 2nd, 4th, ... levels are read right to left. */
export function zigzag(root) {
  throw new Error('write zigzag');
}
The sample tests · tree_levels.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { Node, levels, zigzag } from './tree_levels.mjs';

const lessonTree = () =>
  new Node('M', new Node('E', new Node('B'), new Node('H', new Node('G'))), new Node('S', new Node('P', null, new Node('R')), new Node('W')));

function perfect(height, start = 1) {
  if (height < 0) return null;
  return new Node(start, perfect(height - 1, 2 * start), perfect(height - 1, 2 * start + 1));
}

const range = (from, to, step = 1) => {
  const out = [];
  for (let k = from; step > 0 ? k < to : k > to; k += step) out.push(k);
  return out;
};

test('an empty tree has no levels', () => {
  assert.deepEqual(levels(null), []);
  assert.deepEqual(zigzag(null), []);
});

test('a single node is one level', () => {
  assert.deepEqual(levels(new Node(7)), [[7]]);
  assert.deepEqual(zigzag(new Node(7)), [[7]]);
});

test("the lesson's tree, by levels and in zig-zag", () => {
  assert.deepEqual(levels(lessonTree()), [['M'], ['E', 'S'], ['B', 'H', 'P', 'W'], ['G', 'R']]);
  assert.deepEqual(zigzag(lessonTree()), [['M'], ['S', 'E'], ['B', 'H', 'P', 'W'], ['R', 'G']]);
});

test('missing children leave gaps, and the levels close up around them', () => {
  const t = new Node(1, new Node(2, null, new Node(4, new Node(6))), new Node(3, null, new Node(5, null, new Node(7))));
  assert.deepEqual(levels(t), [[1], [2, 3], [4, 5], [6, 7]]);
  assert.deepEqual(zigzag(t), [[1], [3, 2], [4, 5], [7, 6]]);
});

test('a perfect tree of height 10: level d holds the keys 2^d to 2^(d+1) - 1', () => {
  const got = levels(perfect(10));
  assert.deepEqual(got, range(0, 11).map((d) => range(2 ** d, 2 ** (d + 1))));
  assert.deepEqual(zigzag(perfect(10))[9], range(2 ** 10 - 1, 2 ** 9 - 1, -1));
});

test('a path of 1,200 nodes has 1,200 levels of one node', () => {
  const root = new Node(0);
  let node = root;
  for (let k = 1; k < 1200; k++) {
    node.right = new Node(k);
    node = node.right;
  }
  const got = levels(root);
  assert.equal(got.length, 1200);
  assert.deepEqual(got[got.length - 1], [1199]);
  assert.deepEqual(zigzag(root)[777], [777]);
});
A hint

Keep a queue of the nodes still to visit, starting with the root. At the start of each level, read the queue's length once: that is exactly the number of nodes on this level. Take that many nodes from the front, record their keys and add their children at the back. For zigzag, build the same lists and reverse the ones with an odd index.

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

5 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 5 Which depth-first order visits a node only after both of its subtrees?

    Choose one answer.

    Show the answer to question 1

    Answer: Postorder

    Postorder is left, right, node. That is why it suits work that needs the children's results first, such as computing sizes or evaluating an expression tree.

  2. Question 2 of 5 The keys of a binary search tree come out in increasing order. Which traversal produced them?

    Choose one answer.

    Show the answer to question 2

    Answer: Inorder

    In a binary search tree every key in a node's left subtree is smaller and every key in its right subtree is larger, and inorder lists the left subtree, then the node, then the right subtree, so the keys come out sorted. The lesson's tree shows it: its inorder is B E G H M P R S W.

  3. Question 3 of 5 In the lesson's tree, which key comes straight after H in level order?

    Choose one answer.

    Show the answer to question 3

    Answer: P

    Level order is M E S B H P W G R. Level 2 is B, H, P, W from left to right, so P follows H; G is H's child and comes only on the next level.

  4. Question 4 of 5 A sequence of the same tree's keys ends with the root. Which order can it be?

    Choose one answer.

    Show the answer to question 4

    Answer: Postorder, or inorder if the root has no right subtree

    Postorder always ends with the root, because it visits a node after both of its subtrees. Inorder ends with the root only when the root's right subtree is empty. Preorder and level order always start with the root.

  5. Question 5 of 5 In the lesson's run, what was the largest number of nodes waiting in the level-order queue at one time?

    Type a number.

    Show the answer to question 5

    Answer: 4

    The recorded output says "largest queue: 4 nodes". While level 2 (B, H, P, W) is being taken from the queue, the four nodes of that level have all been waiting together; level order needs memory for about one level at a time.

All three depth-first orders with an explicit stack, and the depth limit reproduced Pro: opens with a Pro, Premium or Ultimate pass

A recursive walk stores the path from the root in the call stack, and CPython stops it after about a thousand nested calls. This part makes the recursive version fail on a 100,000-node path, then writes preorder, inorder and postorder with a stack of their own and checks them against the recursive versions on 1,000 random trees.

In this part:

  • A 100,000-node path against CPython’s recursion limit
  • Preorder: pop a node, push its right child, then its left
  • Inorder: walk down the left spine, then step right
  • Postorder: one stack and the node visited last
  • How large each stack grows, measured

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).

Morris traversal: inorder in O(1) extra memory, with its proof Pro: opens with a Pro, Premium or Ultimate pass

Morris traversal walks a tree in order without any stack: before entering a node’s left subtree it points the last node of that subtree back at the node, and it removes that link on the way out. This part traces the method on the lesson’s tree, proves that the tree comes back unchanged after O(n) work, and reproduces the damage done by stopping early.

In this part:

  • The temporary links, traced on the lesson’s tree
  • Why the tree comes back unchanged, and why the work stays linear: the proof
  • Stopping early corrupts the tree: the failure reproduced
  • When the trade is worth it

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).

Per-level answers, telling orders apart, and five languages Pro: opens with a Pro, Premium or Ultimate pass

Reading the queue’s length before each level gives one answer per level from a single loop. This part builds zig-zag order, side views and level averages, checks every tree shape up to eight nodes to settle when two orders can coincide, and runs the traversals in JavaScript, Java, C++ and Go with their outputs recorded.

In this part:

  • One loop for zig-zag order, side views and level averages
  • When two orders give the same sequence, checked on every shape up to eight nodes
  • The same traversals in JavaScript, Java, C++ and Go

4 runnable examples (C++, Go, Java, JavaScript, Python) · about 4 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 · Warm-up

    Why does an inorder traversal of a binary search tree list its keys in increasing order?

    Inorder visits a node's whole left subtree, then the node, then its whole right subtree. In a binary search tree every key in the left subtree is smaller than the node's key and every key in the right subtree is larger, so at each node the keys come out as the smaller ones, this one, then the larger ones. Induction on the size of the subtree makes it precise: if both subtrees are listed in increasing order, the smaller list, then the node, then the larger list is increasing too, and an empty subtree gives an empty list. The converse also holds: a binary tree whose inorder sequence is strictly increasing is a binary search tree, which gives one correct way to check one.

  2. Mid to senior

    A service walks each customer's category tree recursively. It works for months, then crashes on one customer whose 300,000 categories form a single chain: RecursionError in Python, StackOverflowError in Java. What happened, and how do you fix it without just raising the limit?

    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 must walk a 50-million-node binary search tree in sorted order on a device that cannot spare memory for a stack. Explain Morris traversal, what it costs, and when you would refuse to use it.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    An API returns the keys of a 10-million-key binary search tree in sorted pages of 50, and each request carries only the last key the client saw. How do you serve the next page without walking from the smallest key, and what does a page cost?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Preorder, inorder and postorder differ only in when they visit the node; level order takes the tree one level at a time with a queue.
  • All four take O(n) time. Recursion holds a call for every node on the current path; level order holds up to a whole level.
  • Use postorder when a node needs its children’s results, inorder for a search tree’s sorted keys, preorder to write a tree out, and level order for the nodes nearest the root.
  • Preorder and level order begin with the root, postorder ends with it, and the leaves keep their order in all three depth-first orders.

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.