Serialize And Deserialize Binary Tree
Problem
Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.
Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.
Clarification: The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.
Example 1:

Input: root = [1,2,3,null,null,4,5]
Output: [1,2,3,null,null,4,5]
Example 2:
Input: root = [] Output: []
Brainstorming
Optimal Solution
That’s a perfect place to start. If the problem statement itself is fuzzy, we need to clarify it before we can solve anything.
“What does converting to a string mean?”
Imagine you have a complex object in your computer’s memory, like a binary tree. It’s made of nodes, and each node has pointers (.left, .right) that point to other nodes’ memory addresses.
# In Memory (Conceptual)
Node_A (at memory address 0x1000) -> left: 0x2000, right: 0x3000
Node_B (at memory address 0x2000) -> left: None, right: None
Node_C (at memory address 0x3000) -> left: None, right: None
You cannot just save 0x1000, 0x2000, 0x3000 to a file. Why?
- If you close your program and re-open it, the operating system might give you completely different memory addresses.
- If you send that file to another computer, its memory layout is totally different. The address
0x1000is meaningless over there.
Serialisation means creating a universal, self-contained representation of the tree’s structure and values that is independent of memory addresses. A string is the most common way to do this.
The Goal:
You need to invent a “language” or “format” to describe your tree. Then, you need to write a translator (serialize) that converts your in-memory tree into a string in that language, and another translator (deserialize) that can read a string in your language and perfectly rebuild the original in-memory tree.
Example Analogy: Building a Lego Model
- The In-memory Tree: The fully built, 3D Lego model.
- The
serializefunction: The person who writes the instruction manual. They look at the model and write down a step-by-step string:"Take a red 2x4 brick; place it at the base. Take a blue 2x2 brick; place it on top of the red one to the left..." - The String: The instruction manual itself. It’s just text. You can save it, email it, etc.
- The
deserializefunction: A person who reads the instruction manual (the string) and, without ever having seen the original model, can build an identical copy.
Let’s Brainstorm a “Language” for a Tree.
How can we describe this tree as a string?
1
/ \
2 3
Idea 1 (Simple but flawed): Just list the nodes.
"1,2,3".- Problem: If you
deserializethis, how do you know2is the left child and3is the right? This string could also represent a tree where3is the left child and2is the right. We lost the structural information.
- Problem: If you
Idea 2 (Better): We need to represent the structure. Tree traversals are a great way to visit nodes in a specific order. Let’s try a pre-order traversal.
Root -> Left -> Right- The preorder traversal of this tree is
1, 2, 3. Still the same ambiguous string.
Idea 3 (The Key Insight): We need to also record when a path ends. We need a marker for
Nonechildren.- Let’s use
"#"as ourNonemarker. - Let’s do a pre-order traversal again, but this time, if we encounter a
Nonechild, we record a"#".
- Let’s use
Let’s trace this for our tree:
- Visit root
1. String is now"1,". - Go left to
2.- Visit
2. String is now"1,2,". - Go left from
2. It’sNone. String is now"1,2,#,". - Go right from
2. It’sNone. String is now"1,2,#,#,".
- Visit
- Go right from
1to3.- Visit
3. String is now"1,2,#,#,3,". - Go left from
3. It’sNone. String is now"1,2,#,#,3,#,". - Go right from
3. It’sNone. String is now"1,2,#,#,3,#,#,".
- Visit
Our serialized string is "1,2,#,#,3,#,#". This string is not ambiguous. It contains all the information needed to rebuild the exact original tree.
(Prompt):
- Based on this “preorder traversal with markers for
None” idea, can you write the pseudocode for theserialize(root)function? It will likely be a recursive helper function. - Now for the harder part: think about
deserialize. You have the string"1,2,#,#,3,#,#". How can you read this from left to right to rebuild the tree? (Hint: The process will also be recursive).
okay so now its clear , basically we want the information in a string format that can be used to to construct the tree again. since string is again an immutable type we would need to define it as a global list element
serialize_string = [""]
#... add details
curr_string = serialize_string[0]
curr_string += result # whatever it is None represented as # or the value
# actual implemention below
class TreeNode:
def __init__(self, val=None, left=None, right=None):
self.val = val
self.left = left
self.right = right
def serialize(root: TreeNode):
serialize_string = [""]
def serialize_helper(node:TreeNode):
# Base case
if node is None:
serialize_string[0] += "#"
# Recursive step pre order root->left->right
serialize_string[0] += f"{root.val}"
serialize_helper(root.left)
serialize_helper(root.right)
serialize_helper(root)
return serialize_string[0]
for constructing the result i think preorder itself will not be enough
lets assume the above code gives us "1,2,#,#,3,#,#" when we go about re constructing it we will know that the first element of the string is the root and the next element will be the left node of this root
no i think we can do this we can assume that till the time we don’t hit # in the remaining string that the elements will always be the left element of the current root if we hit # it means we should stop going left and come back
deserailization in progress
1 get root make the tree node
/ go left with the current tree node with the remainig string
2 get root make it a tree node attach is to the global tree
/ go left with the current tree node
# there is nothing more to the left return to root i.e 2 and go right
so we need a way to pass the global tree and attach current nodes whenever they are valid do this in preorder style root->left->right and the base case is if the current zero index is # in the string string just return
# actual implemention below
class TreeNode:
def __init__(self, val=None, left=None, right=None):
self.val = val
self.left = left
self.right = right
def deserialize(serialized_string: str):
# edge case
if serialized_string[0] == "#":
return None
def deserialize_helper(serialized_string):
# base case
if serialized_string[0] == "#":
return
# recursive step
root_val = serialized_string[0]
current_root = TreeNode(root_val)
remaining_string = serialized_string[1:]
current_root.left = deserialize_helper(remaining_string)
current_root.right = deserialize_helper(remaining_string)
return current_root
binary_tree = deserialize_helper(serialized_string)
return binary_tree
- 🍅 (pomodoro::WORK) (duration:: 30m) (begin:: 2025-12-17 10:49) - (end:: 2025-12-17 11:19)
Critique of serialise
Your approach will work, but it has some bugs and can be made cleaner.
- State Management: serialize_string =
[""]and modifying[0]works, but it’s a bit clunky. A simpler way is to have the recursive helper return the string for its subtree, and then combine the results at the caller. - Base Case vs. Recursive Step:
if node is None:
serialize_string[0] += "#" # Missing a comma/delimiter
# This part runs even if node is None, which will crash
serialize_string[0] += f"{root.val}"
serialize_helper(root.left)
```
You need an if/else block. If node is None, you do one thing. If it's not None, you do the pre-order traversal.
```python
def serialize_helper(node):
if node is None:
return "#,"
# root str
root_str = str(node.val) + ","
root_left_str = serialize_helper(node.left)
root_right_str = serialize_helper(node.right)
return root_str + root_left_str + root_right_str
Critique of deserialise (You’re so close!)
Your logic is nearly perfect.
“we can assume that till the time we don’t hit # in the remaining string that the elements will always be the left element of the current root”
This is the key. The preorder string is a stream of data. The call that is building the left subtree will consume some part of this stream, and whatever is left is for the right subtree.
The Problem with Your Code:
You pass remaining_string to both recursive calls.
remaining_string = serialized_string[1:]
current_root.left = deserialize_helper(remaining_string)
current_root.right = deserialize_helper(remaining_string) # Problem!
The deserialize_helper for the right child starts reading from the same remaining_string as the left child did, instead of starting from where the left child finished.
The Solution: How to “Consume” the String?
The easiest way to handle this is to turn the string into a list or a queue, so that when a recursive call processes an element, it’s permanently “consumed” for all other calls.
Let’s convert the string “1,2,#,#,3,#,#” into a list data = ["1", "2", "#", "#", "3", "#", "#"].
Revised Logic for deserialize_helper(data_list):
Get the next element: val = data_list.pop(0) (removes the first element).
Base Case: If val is “#”, you’ve hit a None node. Return None.
Recursive Step:
- If val is a number, create the root = TreeNode(int(val)).
- Build Left: The next elements in the list belong to the left subtree. Recursively call the helper to build it.
root.left = deserialize_helper(data_list) - Build Right: After the root.left call has completely finished (it has consumed all the elements it needs from the list), the next elements in the (now shorter) list belong to the right subtree.
root.right = deserialize_helper(data_list) - Return the root you built.
This structure works because the data_list is a shared, mutable object. When deserialize_helper for the left child pops elements, they are gone for good, and the deserialize_helper for the right child automatically starts reading from the correct position.
class TreeNode:
def __init__(self, val=None, left=None, right=None):
self.val = val
self.left = left
self.right = right
def deserialize_helper(serialized_string:str):
data_list = serialized_string.split(",")
def deserialized_helper(data_list):
val = data_list.pop(0)
if val == "#":
return None
root = TreeNode(int(val))
root.left = deserialized_helper(data_list)
root.right = deserialized_helper(data_list)
return root
tree = deserialized_helper(data_list)
return tree
- 🥤 (pomodoro::BREAK) (duration:: 15m) (begin:: 2025-12-17 11:21) - (end:: 2025-12-17 12:03)
Final solution
class TreeNode:
def __init__(self, val=None, left=None, right=None):
self.val = val
self.left = left
self.right = right
class codec:
def __init__(self):
pass
def _serialize_helper(self, node):
if node is None:
return "#,"
# Pre-order: Root, Left, Right
root_str = str(node.val) + ","
root_left_str = self._serialize_helper(node.left)
root_right_str = self._serialize_helper(node.right)
return root_str + root_left_str + root_right_str
def _deserialize_helper(self, data_list:list[str]):
val = data_list.pop(0)
if val == "#":
return None
# Recursive Step: # 1. Create the current node.
root = TreeNode(int(val))
# 2. Build the left subtree. The recursive call will consume # all necessary tokens from the front of the shared data_list.
root.left = self._deserialize_helper(data_list)
# 3. Build the right subtree. This call automatically starts # from where the left subtree's construction finished.
root.right = self._deserialize_helper(data_list)
return root
def serialize(self, root:TreeNode):
serialized_string = self._serialize_helper(root)
return serialized_string
def deserialize(self, serialized_string:str):
data_list = serialized_string.split(",")
tree = self._deserialize_helper(data_list)
return tree