Algorithm

Serialize and Deserialize Binary Tree

Trees Pattern

Serialize and Deserialize Binary Tree

Design two functions. serialize(root) turns a binary tree into a single string; deserialize(data) turns such a string back into a tree that is structurally identical to the original, with the same values in the same positions. The string format is entirely your choice — you are only required to be able to read back what you wrote. An empty tree must survive the round trip too. This is an ordinary binary tree: no ordering of values may be assumed.

CONSTRAINTS
  • The number of nodes in the tree is in the range [0, 10⁴]
  • -1000 <= Node.val <= 1000
  • Values may repeat, and may be negative
  • You choose the encoding; only the round trip is graded
EXAMPLE 1
Input: root = [1,2,3,null,null,4,5]
Output: round trip reproduces [1,2,3,null,null,4,5]
The rebuilt tree must match node for node, including which children are missing — 2 has none while 3 has two.
EXAMPLE 2
Input: root = []
Output: round trip reproduces []
The empty tree has to serialize to something the reader recognises as 'no tree', not to an empty string that could be mistaken for missing data.
EXAMPLE 3
Input: root = [1,2,null,3]
Output: round trip reproduces [1,2,null,3]
3 hangs on the left of 2, and 2 on the left of 1. An encoding that loses which side a lone child is on would rebuild the wrong tree here.
EXAMPLE 4
Input: root = [7,7,7]
Output: round trip reproduces [7,7,7]
Repeated values are legal, so the encoding cannot rely on values being distinct — a restriction the two reconstruction problems did depend on.
Is the string format fixed?
No, it is yours to choose. Only the round trip is checked, which is why this problem is really about designing an unambiguous encoding.
Can values repeat here?
Yes, unlike in the two-traversal reconstruction problems. Any approach that identifies nodes by value is therefore unavailable.
Must the empty tree be handled?
Yes. Decide up front what a null root serialises to, and make sure the reader treats it as an empty tree rather than as malformed input.
How do I keep negative numbers from breaking the format?
Use a separator that cannot appear inside a value — a comma is fine, since a minus sign only ever leads a token. Fixing that boundary is why splitting on a delimiter beats reading character by character.

A tree is a two-dimensional shape; a string is a line. Serialization asks how to flatten one into the other without losing anything, and deserialization asks how to read it back. The interesting question is what "without losing anything" actually requires.

Why a plain traversal is not enough

Write out a pre-order traversal and hand it over: [1, 2]. The reader knows 1 is the root and 2 is its only child, but not which side it hangs on — the two shapes produce identical output. This is exactly the ambiguity from Construct Tree from Preorder and Inorder, and there we fixed it by supplying a second traversal, with in-order acting as the map of where each root splits.

That fix is unavailable here for two reasons. Values may repeat, and locating a root inside the in-order array by value needs uniqueness. And nothing obliges us to send two arrays — we control the format, so we can fix the ambiguity at its source instead.

Name the gaps and the ambiguity disappears

Where did the information actually go? A traversal lists only the nodes that exist. When node 1 has one child, the output says nothing at all about the missing side, so the reader cannot tell which side is missing. The absences are real structure, and we simply failed to write them down.

So write them down. Emit a marker — say # — for every null link. Now [1, 2] becomes either 1,2,#,#,# (2 on the left, then 2's two nulls, then 1's right null) or 1,#,2,#,# (1's left null, then 2 with its two nulls). Different strings, no ambiguity.

Key Insightwith null markers included, a single pre-order traversal determines the tree completely. The reason is that the reader never has to guess where a subtree ends — it is told. Reading a value means "build a node, and the next thing in the stream is its entire left subtree, followed by its entire right subtree." Reading a # means "this subtree is empty, nothing more to consume." Every token is a self-contained instruction, so a left-to-right scan can rebuild the tree with no lookahead and no boundary arithmetic at all.
python
def serialize(node):
    if node is None:
        return "#"                                  # the gap is data too
    return f"{node.val},{serialize(node.left)},{serialize(node.right)}"

def deserialize(data):
    tokens = iter(data.split(","))                  # read strictly left to right

    def build():
        tok = next(tokens)                          # consume exactly one token
        if tok == "#":
            return None                             # an empty subtree — consumes nothing more
        node = Node(int(tok))
        node.left  = build()                        # the next tokens are the whole left subtree
        node.right = build()                        # then the whole right subtree
        return node

    return build()

Notice the symmetry: the writer recurses node, left, right, and the reader recurses node, left, right. The two functions have the same shape because the reader is replaying the writer's walk.

Crucial Notebuild must consume its token before recursing, and the two recursive calls must be assigned in that order — left, then right. The stream carries no positional information; correctness rests entirely on the reader consuming tokens in exactly the order the writer produced them. This is the same discipline as the shared backward index in Construct Tree from Inorder and Postorder, and it fails the same way: swap the two lines and you get a valid-looking tree that is silently mirrored. A subtlety in some languages: if you write Node(tok, build(), build()) the argument evaluation order may not be guaranteed, so bind the calls to named variables in separate statements.
Worked example:root 1 with children 2 and 3, where 3 has children 4 and 5
Serializing, pre-order with markers: 1, then 2's subtree (2 with two nulls) → 2,#,#, then 3's subtree → 3,4,#,#,5,#,#. The full string is
text
1,2,#,#,3,4,#,#,5,#,#

Reading it back, one token at a time:
- 1 → make node 1, then go build its left.
- 2 → make node 2, then build its left: # → null. Then its right: # → null. Node 2 is finished and returns as 1's left child.
- 3 → make node 3, build its left: 4, whose two tokens are # and #, so 4 is a leaf. Then 3's right: 5, likewise a leaf.
- Node 3 returns as 1's right child; the stream is exhausted; the tree is complete.

Eleven tokens for five nodes — six of them markers. That is the price of the format, and it is a bargain compared with sending two full traversals.

Cost, and the idea that generalises

Serialization visits every node once and every null link once. A tree with N nodes has exactly N + 1 null links, so the string holds 2N + 1 tokens: O(N) time and O(N) output. Deserialization consumes each token once, also O(N), with O(H) recursion depth on top of the O(N) token list. A level-order (BFS) encoding with markers is an equally valid alternative — it produces the familiar LeetCode array format and avoids deep recursion — but the pre-order version is shorter and its reader needs no queue of pending parents.

The transferable principle is worth stating carefully, because it reaches well past trees: a structure can be flattened into a line without loss exactly when the reader can tell where each part ends. You get that either by sending a second view that pins down the boundaries (the two-traversal reconstructions), or by writing explicit terminators so each part announces its own end (null markers here). Length prefixes are the third common variant, and every real serialization format — JSON, protobuf, network protocols — picks one of these three. When you next design an encoding, ask the same question first: what tells the reader where this piece stops?

Interactive Strategy Visualization

Object Persistence

Serialization & Deserialization Pipeline

BINARY TREE
SERIAL STREAM
1,2,x,x,3,4,x,x,5
RECONSTRUCTED
PREORDER STRATEGY
Use recursive Preorder to ensure the root is always written/read before its children.
NULL MARKING
Explicitly mark empty children (usually with 'null' or 'x') to preserve the tree structure without needing extra traversals.
Ambiguous Without Null Markers
O(N) Time · O(N) Space Pre-order With Null Markers
O(N) Time · O(N) Space Level-order Encoding