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

Building and serialising binary trees

Rebuild a binary tree from its preorder and inorder lists, see why one traversal is not enough, and write a tree out as text and read it back safely.

  • Intermediate
  • 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: Why one traversal is not enough · Building a tree from preorder and inorder · Serialising binary trees with markers · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • Rebuild a binary tree from its preorder and inorder sequences
  • Serialise and deserialise a tree
  • Explain which traversal pairs determine a tree uniquely

Before you start

Building a binary tree back from a list of its keys works only when the list keeps enough of the shape. One traversal on its own does not: the preorder A B fits a tree in which B is A’s left child and one in which it is the right child. With distinct keys, preorder and inorder together fix the tree. The first preorder key is the root, and its place in the inorder list says which keys lie to its left and which to its right, so the same step repeats on each side. Serialising binary trees for a file, a database or a test case takes another route: one traversal, with a marker for every empty child place, so the text records the shape itself. This lesson rebuilds a family tree from its two lists, writes trees out with markers and reads them back, and checks 1,000 random round trips.

Why one traversal is not enough

A traversal lists every key but drops the positions of the empty child places. Take the keys of a preorder and place them, in order, into any binary tree shape with that many nodes: every shape produces the same preorder. Three keys therefore fit 5 different trees, and eight keys fit 1,430, the number of binary tree shapes with eight nodes. Something has to put the lost information back, either a second traversal that fixes left and right, or markers that record each empty place where it occurs.

Building a tree from preorder and inorder

Asha, first in the preorder, is found in the inorder, where she splits the other keys into four on the left and three on the right.PreorderAsha | Bina Chetan Dev Hari | Esha Farid Gitaroot | left subtree, 4 keys | right subtree, 3 keysInorderChetan Bina Dev Hari | Asha | Farid Esha Gitaleft subtree, 4 keys | root | right subtree, 3 keysAshaleft: Bina, Chetan, Dev, Hariright: Esha, Farid, GitaRepeat on each side:Bina is the next root, Esha the one after its subtreefind the first key, Asha4 keys before her, 3 after

The first preorder key is the root, and the inorder list says which keys go left and which go right

Text description of the diagram

The diagram shows the first step of rebuilding the family tree, from top to bottom.

  1. The preorder list is Asha, then Bina, Chetan, Dev and Hari, then Esha, Farid and Gita. Its first key, Asha, is the root.
  2. In the inorder list, Chetan, Bina, Dev and Hari come before Asha and Farid, Esha and Gita after her.
  3. So Asha's left subtree holds the four keys Bina, Chetan, Dev and Hari, which are also the next four keys of the preorder, and her right subtree holds Esha, Farid and Gita, the last three.
  4. The same step is repeated on each side: Bina is the root of the left subtree, and Esha, the first key after it, is the root of the right one.
  1. The first key of the preorder is the root.
  2. Find it in the inorder: the keys before it form the left subtree and the keys after it the right subtree.
  3. If the left subtree has k keys, the next k keys of the preorder are its preorder, and the rest belong to the right subtree.
  4. Repeat the same steps on each side until no keys are left.

The program below follows these steps for a family of eight. A dictionary gives each key’s position in the inorder list, so finding a root costs O(1), and a counter remembers how many preorder keys have become nodes:

A family tree rebuilt from its preorder and inorder lists Python · rebuild/family.py
"""A family tree rebuilt from its preorder and inorder lists, traced, printed and walked in postorder."""

preorder = ["Asha", "Bina", "Chetan", "Dev", "Hari", "Esha", "Farid", "Gita"]
inorder = ["Chetan", "Bina", "Dev", "Hari", "Asha", "Farid", "Esha", "Gita"]


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


def rebuild(preorder, inorder, trace=None):
    where = {key: i for i, key in enumerate(inorder)}     # each key's position in the inorder list
    if len(where) != len(inorder) or sorted(preorder) != sorted(inorder):
        raise ValueError("the two lists must hold the same distinct keys")
    taken = 0                                              # how many preorder keys have become nodes

    def build(lo, hi):
        """The subtree whose keys are inorder[lo:hi]; its root is the next unused preorder key."""
        nonlocal taken
        if lo == hi:
            return None
        node = Node(preorder[taken])
        taken += 1
        mid = where[node.key]
        if trace is not None:
            trace.append(f"{node.key:<7} left: {' '.join(inorder[lo:mid]) or '-':<22} right: {' '.join(inorder[mid + 1:hi]) or '-'}")
        node.left = build(lo, mid)
        node.right = build(mid + 1, hi)
        return node

    return build(0, len(inorder))


def show(node, prefix=""):
    """Prints the tree sideways: each child under its parent, marked L or R."""
    for side, child in (("L", node.left), ("R", node.right)):
        if child:
            print(f"{prefix}{side} {child.key}")
            show(child, prefix + "  ")


def postorder(node, out):
    if node:
        postorder(node.left, out)
        postorder(node.right, out)
        out.append(node.key)
    return out


steps = []
root = rebuild(preorder, inorder, steps)
print("\n".join(steps))
print(root.key)
show(root)
print("postorder:", " ".join(postorder(root, [])))

Output

Asha    left: Chetan Bina Dev Hari   right: Farid Esha Gita
Bina    left: Chetan                 right: Dev Hari
Chetan  left: -                      right: -
Dev     left: -                      right: Hari
Hari    left: -                      right: -
Esha    left: Farid                  right: Gita
Farid   left: -                      right: -
Gita    left: -                      right: -
Asha
L Bina
  L Chetan
  R Dev
    R Hari
R Esha
  L Farid
  R Gita
postorder: Chetan Hari Dev Bina Farid Gita Esha Asha

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

The trace shows each split. Asha divides the inorder into four keys on the left and three on the right; Bina, the next preorder key, divides the left four into Chetan and Dev with Hari, and so on until every key has its place. Building the tree this way handles each key once, so with the dictionary it takes O(n) time. Searching the inorder list for every root instead would take O(n²) steps on a path. The recursion goes as deep as the tree is tall.

Serialising binary trees with markers

Serialising binary trees needs no second list if the text marks every empty place. The usual way is preorder with a marker such as # wherever a child is missing: a tree of n keys becomes n keys and n + 1 markers, and a reader rebuilds it by taking tokens in order, making a node for a key and an empty place for a marker. Here the family tree goes out and comes back, followed by the level-order list form that many test-case formats use, and 1,000 random trees make the same round trip:

Preorder with markers: written out, read back, 1,000 round trips Python · serialise/roundtrip.py
"""Preorder with a marker for every empty place: write a tree out, read it back, and check 1,000 random round trips."""
import random
from collections import deque


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


def serialise(root):
    """Preorder, with '#' for each empty child place: n keys and n + 1 markers."""
    out, stack = [], [root]
    while stack:
        node = stack.pop()
        if node is None:
            out.append("#")
        else:
            out.append(str(node.key))
            stack += [node.right, node.left]      # the left subtree is written first
    return " ".join(out)


def deserialise(text):
    tokens = iter(text.split())

    def read():
        token = next(tokens)                       # StopIteration here means the text was cut short
        if token == "#":
            return None
        node = Node(token)
        node.left = read()
        node.right = read()
        return node

    root = read()
    if next(tokens, None) is not None:
        raise ValueError("text left over after the tree")
    return root


def level_order_format(root):
    """The common list form: level order with 'null' for empty places, trailing nulls dropped."""
    out, queue = [], deque([root])
    while queue:
        node = queue.popleft()
        out.append("null" if node is None else str(node.key))
        if node is not None:
            queue += [node.left, node.right]
    while out and out[-1] == "null":
        out.pop()
    return "[" + ",".join(out) + "]"


family = Node("Asha", Node("Bina", Node("Chetan"), Node("Dev", None, Node("Hari"))),
              Node("Esha", Node("Farid"), Node("Gita")))
text = serialise(family)
print(text)
print(f"{len(text.split())} tokens: 8 keys and {text.split().count('#')} markers")
print(level_order_format(family))
print("read back and written again:", serialise(deserialise(text)) == text)


def random_tree(n, rng):
    if n == 0:
        return None
    root = Node(0)
    places = [(root, "left"), (root, "right")]
    for k in range(1, n):
        parent, side = places.pop(rng.randrange(len(places)))
        child = Node(k)
        setattr(parent, side, child)
        places += [(child, "left"), (child, "right")]
    return root


rng = random.Random(11)
failures = 0
for _ in range(1000):
    t = random_tree(rng.randint(0, 60), rng)
    s = serialise(t)
    failures += serialise(deserialise(s)) != s
print("1,000 random trees of 0 to 60 nodes, serialised, read back and serialised again; differences:", failures)

Output

Asha Bina Chetan # # Dev # Hari # # Esha Farid # # Gita # #
17 tokens: 8 keys and 9 markers
[Asha,Bina,Esha,Chetan,Dev,Farid,Gita,null,null,null,Hari]
read back and written again: True
1,000 random trees of 0 to 60 nodes, serialised, read back and serialised again; differences: 0

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

The family tree becomes 17 tokens, 8 keys and 9 markers, and reading it back gives a tree that writes out as exactly the same text. None of the 1,000 random trees, from empty up to 60 nodes, changed on the way. The level-order form writes the tree level by level with null for empty places and leaves out the markers at the end.

The choices compared in depth

Preorder with postorder fixes a tree only when no node has exactly one child. Postorder with inorder, or level order with inorder, fixes a tree just as preorder with inorder does. A rebuild driven by an explicit stack handles a 100,000-node path that the recursive version cannot. And the two text formats differ in size: level order is shorter for complete trees, preorder for deep or sparse ones.

Exercise: postorder from two orders

The exercise gives the preorder and inorder of a tree and asks for its postorder, on the family tree, on paths of 900 nodes and on a random tree of 3,000.

Exercise · Medium · Python, JavaScript

Postorder from preorder and inorder

A binary tree with distinct keys is given only by its preorder and inorder lists. Write postorder_from(preorder, inorder) (JavaScript: postorderFrom(preorder, inorder)), which returns the tree's postorder as a list. Both lists hold the same keys; an empty tree gives an empty list.

For the lesson's family tree, preorder Asha Bina Chetan Dev Hari Esha Farid Gita and inorder Chetan Bina Dev Hari Asha Farid Esha Gita give the postorder Chetan Hari Dev Bina Farid Gita Esha Asha. The tests also use paths of 900 nodes leaning left and right, and a random tree of 3,000 nodes, so finding each root by scanning the inorder list works but is slow; a dictionary from key to inorder position keeps the whole job O(n). You may build node objects or produce the postorder straight from the two lists.

Python · Starter code · two_orders.py

def postorder_from(preorder, inorder):
    """The postorder of the binary tree with these preorder and inorder lists (distinct keys)."""
    raise NotImplementedError("write postorder_from")
The sample tests · test_two_orders.py
import random

from two_orders import postorder_from


def orders_of(n, seed):
    """Preorder, inorder and postorder of a random binary tree with keys 0 to n - 1, built without deep recursion."""
    rng = random.Random(seed)
    left, right = [None] * n, [None] * n
    places = [(0, "L"), (0, "R")]
    for k in range(1, n):
        p, side = places.pop(rng.randrange(len(places)))
        (left if side == "L" else right)[p] = k
        places += [(k, "L"), (k, "R")]
    pre, ino, post = [], [], []
    stack = [(0, False)]
    while stack:
        v, done = stack.pop()
        if done:
            post.append(v)
            continue
        pre.append(v)
        stack.append((v, True))
        if right[v] is not None:
            stack.append((right[v], False))
        if left[v] is not None:
            stack.append((left[v], False))
    # inorder: a second walk
    stack, v = [], 0
    while stack or v is not None:
        while v is not None:
            stack.append(v)
            v = left[v]
        v = stack.pop()
        ino.append(v)
        v = right[v]
    return pre, ino, post


def test_empty_tree():
    """an empty tree has an empty postorder"""
    assert postorder_from([], []) == []


def test_single_key():
    """one key is its own postorder"""
    assert postorder_from(["Asha"], ["Asha"]) == ["Asha"]


def test_family_tree():
    """the lesson's family tree"""
    pre = ["Asha", "Bina", "Chetan", "Dev", "Hari", "Esha", "Farid", "Gita"]
    ino = ["Chetan", "Bina", "Dev", "Hari", "Asha", "Farid", "Esha", "Gita"]
    assert postorder_from(pre, ino) == ["Chetan", "Hari", "Dev", "Bina", "Farid", "Gita", "Esha", "Asha"]


def test_left_and_right_paths():
    """paths of 900 nodes: leaning left the inorder is reversed, leaning right it equals the preorder"""
    pre = list(range(900))
    assert postorder_from(pre, pre[::-1]) == pre[::-1]
    assert postorder_from(pre, list(pre)) == pre[::-1]


def test_random_tree():
    """a random tree of 3,000 nodes"""
    pre, ino, post = orders_of(3_000, seed=4)
    assert postorder_from(pre, ino) == post

JavaScript · Starter code · two_orders.mjs

/** The postorder of the binary tree with these preorder and inorder lists (distinct keys). */
export function postorderFrom(preorder, inorder) {
  throw new Error('write postorderFrom');
}
The sample tests · two_orders.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { postorderFrom } from './two_orders.mjs';

// Preorder, inorder and postorder of a random binary tree with keys 0 to n - 1 (a small fixed generator, no recursion).
function ordersOf(n, seed) {
  let s = seed;
  const next = (m) => { s = (s * 1103515245 + 12345) % 2147483648; return s % m; };
  const left = new Array(n).fill(null);
  const right = new Array(n).fill(null);
  const places = [[0, 'L'], [0, 'R']];
  for (let k = 1; k < n; k++) {
    const [p, side] = places.splice(next(places.length), 1)[0];
    (side === 'L' ? left : right)[p] = k;
    places.push([k, 'L'], [k, 'R']);
  }
  const pre = [], ino = [], post = [];
  const stack = [[0, false]];
  while (stack.length) {
    const [v, done] = stack.pop();
    if (done) { post.push(v); continue; }
    pre.push(v);
    stack.push([v, true]);
    if (right[v] !== null) stack.push([right[v], false]);
    if (left[v] !== null) stack.push([left[v], false]);
  }
  const st = [];
  let v = 0;
  while (st.length || v !== null) {
    while (v !== null) { st.push(v); v = left[v]; }
    v = st.pop();
    ino.push(v);
    v = right[v];
  }
  return { pre, ino, post };
}

test('an empty tree has an empty postorder', () => {
  assert.deepEqual(postorderFrom([], []), []);
});

test('one key is its own postorder', () => {
  assert.deepEqual(postorderFrom(['Asha'], ['Asha']), ['Asha']);
});

test("the lesson's family tree", () => {
  const pre = ['Asha', 'Bina', 'Chetan', 'Dev', 'Hari', 'Esha', 'Farid', 'Gita'];
  const ino = ['Chetan', 'Bina', 'Dev', 'Hari', 'Asha', 'Farid', 'Esha', 'Gita'];
  assert.deepEqual(postorderFrom(pre, ino), ['Chetan', 'Hari', 'Dev', 'Bina', 'Farid', 'Gita', 'Esha', 'Asha']);
});

test('paths of 900 nodes: leaning left the inorder is reversed, leaning right it equals the preorder', () => {
  const pre = Array.from({ length: 900 }, (_, i) => i);
  const reversed = [...pre].reverse();
  assert.deepEqual(postorderFrom(pre, reversed), reversed);
  assert.deepEqual(postorderFrom(pre, [...pre]), reversed);
});

test('a random tree of 3,000 nodes', () => {
  const { pre, ino, post } = ordersOf(3000, 4);
  assert.deepEqual(postorderFrom(pre, ino), post);
});
A hint

The first key of the preorder is the root, and its position in the inorder splits the remaining keys: everything to its left belongs to the left subtree, everything to its right to the right subtree. Write a helper for the keys inorder[lo:hi] that takes the next unused preorder key as its root, handles the left part, then the right part, and only then appends the root, which is exactly postorder.

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 Three distinct keys are listed in preorder. How many differently shaped binary trees have exactly this preorder?

    Type a number.

    Show the answer to question 1

    Answer: 5

    Preorder fixes the root (the first key) and the order of the rest, but not the shape: the other two keys can sit as a left chain, a right chain, a left-then-right or right-then-left bend, or as two children of the root. That is 5 shapes, the number of binary trees with 3 nodes.

  2. Question 2 of 5 Preorder is A B C and inorder is B A C. What is the tree?

    Choose one answer.

    Show the answer to question 2

    Answer: A is the root, with B as its left child and C as its right child

    A is first in the preorder, so it is the root. In the inorder, B stands before A and C after it, so B is alone in the left subtree and C alone in the right one.

  3. Question 3 of 5 A tree of 12 keys is written in preorder with a '#' for every empty child place. How many '#' markers does the text contain?

    Type a number.

    Show the answer to question 3

    Answer: 13

    A binary tree with n nodes has n + 1 empty child places: 2n places in all, minus the n − 1 used by edges. With 12 keys that is 13 markers, so the text has 25 tokens.

  4. Question 4 of 5 Which pair of traversals does not, in general, determine a binary tree whose keys are all different?

    Choose one answer.

    Show the answer to question 4

    Answer: Preorder and postorder

    Without inorder nothing says on which side a node's only child sits: preorder A B with postorder B A fits B as A's left child and as its right child. Each pair that includes inorder determines the tree.

  5. Question 5 of 5 Why does a dictionary from key to inorder position make rebuilding take O(n) time?

    Choose one answer.

    Show the answer to question 5

    Answer: Each root's place in the inorder is found in O(1) instead of by scanning its part of the list

    Scanning for each root costs as much as the part of the inorder list it has to search, which adds up to about n²/2 steps on a path. With the dictionary each of the n roots is one lookup.

Which traversal pairs determine a tree: proofs, counterexamples and every shape checked Pro: opens with a Pro, Premium or Ultimate pass

Inorder is the list that decides between left and right. This part proves that preorder, postorder or level order together with inorder fixes a tree with distinct keys, counts exactly how many trees share one preorder and postorder, rebuilds full trees from those two lists, and checks every claim on every shape up to nine nodes.

In this part:

  • Preorder and inorder fix the tree: the proof by induction
  • Postorder or level order instead of preorder
  • Preorder with postorder: 2^k trees for k one-child nodes
  • Full binary trees: rebuilding from preorder and postorder
  • Repeated keys break every pair

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

Linear-time and stack-safe rebuilding, measured on 100,000 nodes Pro: opens with a Pro, Premium or Ultimate pass

The recursive rebuild has two costs that grow with the input: a scan of the inorder list for each root, and one call for each level of the tree. This part counts the first, removes it with a dictionary, and replaces the recursion with a stack that rebuilds paths of 100,000 nodes.

In this part:

  • Scanning for each root is quadratic: the counts
  • The stack rebuild, step by step, and why it is correct
  • 100,000 nodes: recursion against the stack, measured
  • Checking that two lists belong together

1 runnable example (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).

Formats compared, parsing untrusted text safely, and five languages Pro: opens with a Pro, Premium or Ultimate pass

A serialised tree is often read back by a different program, sometimes from text that nobody has checked. This part measures the two common formats on three shapes, writes a reader that rejects bad input with a precise message, shows why nested JSON suits deep trees badly, and runs the round trip in JavaScript, Java, C++ and Go.

In this part:

  • Preorder text or level-order text: sizes measured on three shapes
  • A reader that rejects bad input, with the position of the problem
  • Why not nested JSON or pickle: the failure reproduced
  • The round trip in JavaScript, Java, C++ and Go

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

Interview questions

  1. Fresher · Warm-up

    Why can't a preorder sequence alone rebuild a binary tree, while preorder with a marker for every empty child can?

    A preorder lists the keys but not the empty child places, so it never says where a subtree ends. The preorder 1, 2 fits a tree in which 2 is the left child of 1 and one in which it is the right child, and with n keys every binary tree shape with n nodes gives the same preorder. Markers put back exactly what was lost. Reading the tokens in order, a key starts a node whose left subtree comes next and whose right subtree follows, and a marker says that a subtree is empty, so every token has one meaning and the tree is rebuilt in a single pass. A tree of n keys needs n + 1 markers, one for each empty child place.

  2. Fresher to mid

    A grader receives the preorder and inorder of a tree with up to 200,000 distinct keys and must print the postorder, in Python, within two seconds. What algorithm do you use, and what breaks in the obvious version?

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

  3. Mid to senior

    Users build decision trees of up to 5,000 nodes in a web app, and you must store 20 million of them in a database text column and load them in browsers and in a Java service. Which text format do you choose, and how do you parse it safely?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A colleague rebuilds trees from their preorder and postorder. On one 6-node tree the rebuilt tree prints the same two sequences but is not the original. Is the code wrong? When do preorder and postorder determine a tree, and how many trees can share one pair?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • One traversal loses the empty places, so it fits every shape with that many nodes.
  • With distinct keys, preorder and inorder fix a tree: the first preorder key is the root, and its inorder position splits the rest.
  • A dictionary of inorder positions makes the rebuild O(n); scanning for each root is O(n²) on a path.
  • Preorder with a marker for every empty place records the shape by itself: n keys, n + 1 markers, and a round trip that gives back the same text.

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.