Serialize and Deserialize Binary Tree

Difficulty: Hard 🏷️ Pattern: Tree Design (Pre-order DFS) 🏢 Asked at: PhonePe, Amazon, Google, Meta


Problem

Design two functions: serialize(root) converts a binary tree into a string, and deserialize(data) reconstructs the exact same tree from that string. Any consistent format is fine as long as the round-trip is lossless.


Approach

Why null markers matter

An in-order traversal alone can’t rebuild a tree (structure is ambiguous). But a pre-order traversal that also records nulls captures the full structure uniquely: the position of each # tells you where a subtree ends.

Serialize — pre-order DFS

Visit root, then left, then right. Emit node.val for real nodes and a sentinel (#) for nulls, separated by commas.

     1
    / \
   2   3
      / \
     4   5

→ "1,2,#,#,3,4,#,#,5,#,#"

Deserialize — consume tokens in the same order

Read tokens left to right. Each call: if the token is #, return null; otherwise create the node and recursively build its left subtree, then its right. Because we wrote pre-order, we read pre-order — the recursion mirrors the encoding exactly.


Complexity

  Time Space
Serialize O(n) O(n) output + O(h) stack
Deserialize O(n) O(n) tokens + O(h) stack

Solution

public String serialize(TreeNode root) {
    StringBuilder sb = new StringBuilder();
    build(root, sb);
    return sb.toString();
}
private void build(TreeNode node, StringBuilder sb) {
    if (node == null) { sb.append("#,"); return; }
    sb.append(node.val).append(",");
    build(node.left, sb);
    build(node.right, sb);
}

public TreeNode deserialize(String data) {
    Queue<String> q = new LinkedList<>(Arrays.asList(data.split(",")));
    return parse(q);
}
private TreeNode parse(Queue<String> q) {
    String tok = q.poll();
    if (tok.equals("#")) return null;
    TreeNode node = new TreeNode(Integer.parseInt(tok));
    node.left = parse(q);
    node.right = parse(q);
    return node;
}
def serialize(root):
    out = []
    def build(node):
        if not node:
            out.append("#")
            return
        out.append(str(node.val))
        build(node.left)
        build(node.right)
    build(root)
    return ",".join(out)

def deserialize(data):
    tokens = iter(data.split(","))
    def parse():
        tok = next(tokens)
        if tok == "#":
            return None
        node = TreeNode(int(tok))
        node.left = parse()
        node.right = parse()
        return node
    return parse()
string serialize(TreeNode* root) {
    string s;
    function<void(TreeNode*)> build = [&](TreeNode* n) {
        if (!n) { s += "#,"; return; }
        s += to_string(n->val) + ",";
        build(n->left); build(n->right);
    };
    build(root);
    return s;
}

TreeNode* deserialize(string data) {
    stringstream ss(data);
    string tok;
    function<TreeNode*()> parse = [&]() -> TreeNode* {
        getline(ss, tok, ',');
        if (tok == "#") return nullptr;
        TreeNode* node = new TreeNode(stoi(tok));
        node->left = parse();
        node->right = parse();
        return node;
    };
    return parse();
}

Try It Yourself

Write your solution and run it live in C++, Java, Python, Go, Rust, and more — right here in the browser.

▶ Try it live
stdin
Output
Run your code to see output here.

Key Insight

Pre-order traversal plus explicit null markers is a bijection with the tree — one unique string per tree and vice versa. Because encode and decode both walk in pre-order, the decode recursion is a mirror image of the encode recursion.


Follow-ups



Drop a comment below if you want the level-order (BFS) encoding 👇

Discussion

Newest first
You