Trees · Binary Tree Fundamentals

Binary Tree Serialize and Deserialize

Encoding a tree's shape into a flat, unambiguous string by explicitly recording null children (not just present ones), so that decoding can rebuild the exact same structure by reading tokens in the same order they were written - a level-order (BFS) encoding paired with a level-order decoding.

  • 7 min read
  • 12 practice questions
  • 1 graded problem
On this page
  1. Description
  2. The approach
  3. A second flavor: level order with a queue
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Serializing a tree turns it into a flat string you can store or send. Deserializing rebuilds the exact same tree. A list of values alone loses the tree's shape. So the encoding also writes a marker for every missing child. With those markers, one traversal is enough to rebuild the tree, as long as the encoder and the decoder use the same one.

We'll go from simple to more technical:

  1. The pattern: preorder with null markers, and one shared position in the string.
  2. A second flavor: level order with null markers and a queue.
  3. Where serialized trees live in production (syntax trees, query plans, caches).
  4. The bugs that rebuild a different tree without any error.

The approach

Write the tree in preorder: the node, then its left subtree, then its right subtree. Write # wherever a child is missing. To decode, read the tokens in the same order. Each call reads one token. A number makes a node, and the call then builds its left and right subtrees from the tokens that follow. A # means "no node here", and the call returns.

Take this tree:

5

2

9

7

11

5

2

9

7

11

Its preorder encoding is 5,2,#,#,9,7,#,#,11,#,#. The decoder reads it like this:

TokensWhat the decoder does
5makes the root, then builds its left subtree
2makes 2 as the left child of 5
#, #2 has no children, so the left subtree of 5 is done
9makes 9 as the right child of 5
7, #, #makes 7 as the left child of 9, a leaf
11, #, #makes 11 as the right child of 9, a leaf

Java

void serialize(TreeNode node, StringBuilder sb) {
    if (node == null) { sb.append("#,"); return; }
    sb.append(node.val).append(",");
    serialize(node.left, sb);
    serialize(node.right, sb);
}

TreeNode deserialize(Iterator<String> tokens) {
    String val = tokens.next();
    if (val.equals("#")) return null;
    TreeNode node = new TreeNode(Integer.parseInt(val));
    node.left = deserialize(tokens);    // consumes exactly this subtree's share of tokens...
    node.right = deserialize(tokens);   // ...before this call reads its own
    return node;
}

Python

def serialize(node: TreeNode | None, out: list[str]) -> None:
    if node is None:
        out.append("#")
        return
    out.append(str(node.val))
    serialize(node.left, out)
    serialize(node.right, out)


def deserialize(tokens) -> TreeNode | None:    # tokens: ONE shared iterator, iter(data.split(","))
    val = next(tokens)
    if val == "#":
        return None
    node = TreeNode(int(val))
    node.left = deserialize(tokens)    # consumes exactly this subtree's share of tokens...
    node.right = deserialize(tokens)   # ...before this call reads its own
    return node

Every call shares one iterator, so there's one position in the string. The left call reads exactly the tokens of the left subtree and stops. The next unread token is then the start of the right subtree.

A second flavor: level order with a queue

Level order writes the tree row by row, with null for each missing child. The tree above becomes 5,2,9,null,null,7,11,null,null,null,null. The encoder is a breadth-first walk that writes null instead of skipping missing children. The decoder runs the same walk. It takes a node from the queue and reads the next two tokens as that node's children:

Java

TreeNode deserialize(String data) {
    String[] tokens = data.split(",");
    if (data.isEmpty() || tokens[0].equals("null")) return null;
    TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));
    Queue<TreeNode> queue = new ArrayDeque<>(List.of(root));
    for (int i = 1; !queue.isEmpty(); i += 2) {
        TreeNode node = queue.poll();
        if (!tokens[i].equals("null")) { node.left = new TreeNode(Integer.parseInt(tokens[i])); queue.offer(node.left); }
        if (!tokens[i + 1].equals("null")) { node.right = new TreeNode(Integer.parseInt(tokens[i + 1])); queue.offer(node.right); }
    }
    return root;
}

Python

from collections import deque


def deserialize(data: str) -> TreeNode | None:
    tokens = data.split(",")
    if not data or tokens[0] == "null":
        return None
    root = TreeNode(int(tokens[0]))
    queue, i = deque([root]), 1
    while queue:
        node = queue.popleft()
        node.left = None if tokens[i] == "null" else TreeNode(int(tokens[i]))
        node.right = None if tokens[i + 1] == "null" else TreeNode(int(tokens[i + 1]))
        queue.extend(child for child in (node.left, node.right) if child)
        i += 2
    return root

Both sides walk the tree in the same order, so the decoder always knows which node the next two tokens belong to.

Where you'll see it

Every tree that's saved to disk, sent over a network or cached goes through this, from a compiler's syntax tree to a database's cached query plan. The order is your choice, as long as the writer and the reader agree on it.

Which tool for which job?

You needUse
Any binary tree, simple codepreorder with null markers and a shared iterator
Level-by-level reading (the format most coding sites use)level order with null markers
A BSTpreorder without nulls - the bounds recover the shape
Input from outside (files, network)an iterative decoder, plus validation
Format changes over timea version marker at the start

Common mistakes

MistakeWhat happensDo this instead
Skipping null childrendifferent trees share one encodinga marker for every null
Encoding in one order, decoding in anothera different tree, no errorone format, documented and tested both ways
A heap-style array (children at 2i+1, 2i+2)exponential size for skewed treesnull markers only under real nodes
A recursive decoder on untrusted inputdeep input overflows the stackdecode iteratively, limit depth
Splitting on a separator that can appear in valuesvalues break apartescape, or use a length prefix

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. An encoding writes only the values, with no null markers. Why can't the decoder rebuild the tree?

Show answer

Different trees give the same string. A root 5 with a left child 2, and a root 5 with a right child 2, both encode as 5,2. The markers record where children are missing, and that is the tree's shape. With them, the two trees become 5,2,#,#,# and 5,#,2,#,#.

2. A tree is written in level order, but the decoder reads the tokens as if they were preorder. What happens?

Show answer

You get a different tree, not an error. Neither side can check which order the other one used. The result looks like a perfectly valid tree, just not the one that was saved. A round-trip test catches it.

3. Why must the left and right calls in the preorder decoder share one iterator, instead of each getting its own?

Show answer

The shared iterator is the position in the string. When the left call returns, the next unread token is where the right subtree starts. With a fresh iterator per call, every call starts again from the root's token. It reads the root, recurses left, reads the root again, and never stops until the stack overflows.

Key takeaways

  • Null markers record the tree's shape, so one traversal is enough to rebuild it.
  • The encoder and decoder must use the same order. A mismatch builds a different tree with no error.
  • Preorder with one shared iterator and level order with a queue are both fine choices.
  • Test with a round trip: deserialize(serialize(tree)) must equal the original.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needUse
Any tree, simplepreorder + null markers
Level-by-levellevel order + null markers
A BSTpreorder, no markers
Untrusted inputiterative decoder + limits
Evolving formata version marker

Snippets worth knowing by heart

Java

void encode(TreeNode n, StringBuilder sb) {
    if (n == null) { sb.append("#,"); return; }          // every null is written
    sb.append(n.val).append(',');
    encode(n.left, sb); encode(n.right, sb);
}
TreeNode decode(Iterator<String> it) {                    // the same order, one shared iterator
    String t = it.next();
    if (t.equals("#")) return null;
    TreeNode node = new TreeNode(Integer.parseInt(t));
    node.left = decode(it);
    node.right = decode(it);
    return node;
}

Python

def decode_iterative(tokens):
    if tokens[0] == "#":
        return None
    root = TreeNode(int(tokens[0]))
    stack = [[root, 0]]                      # node, children filled so far
    for t in tokens[1:]:
        node = None if t == "#" else TreeNode(int(t))
        parent = stack[-1]
        if parent[1] == 0:
            parent[0].left = node
        else:
            parent[0].right = node
        parent[1] += 1
        if parent[1] == 2:
            stack.pop()
        if node:
            stack.append([node, 0])
    return root

The traps

  • No null markers: different trees share one encoding. Write a marker for every null child.
  • Mismatched orders: encoding in one order and decoding in another gives a different tree, silently. Fix one format and test both ways.
  • Heap-style arrays: skewed trees need exponential space. Write markers only under real nodes.
  • Recursive decoders on untrusted input: deep input overflows the stack. Decode iteratively and limit depth.
  • Separators inside values: values split apart. Escape them, or use a length prefix.

Staff-level points, one line each

  • n nodes → 2n + 1 tokens (n + 1 nulls): 1,000 → 2,001.
  • A 20-node chain as a heap array: 1,048,575 slots.
  • Level order read as preorder: no error, all tokens used, a different tree.
  • 5,000-deep input: recursive decoder failed, iterative decoder didn't.

Say it in an interview

  • "I write a marker for every null, so the shape is recoverable from one traversal."
  • "Decode uses the same order, with one shared iterator."
  • "That's 2n + 1 tokens, O(n) both ways."
  • "For untrusted input I'd decode iteratively and validate."

Practice

This skill comes with 12 multiple-choice questions that tell you which misconception a wrong answer reveals and 1 graded problem. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Binary Tree Serialize and Deserialize free

Serialize and Deserialize a Binary Tree

MediumProCoding problemJava · Python

Write two functions: serialize turns a binary tree into a string, and deserialize turns that string back into the same tree. The string format is yours to design. The only rule is that deserialize(serialize(root)) rebuilds the original tree, shape and values.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Binary Tree Serialize and Deserialize free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime