Serialize and Deserialize Binary Tree

Hard
Solve this question on LeetCode

Design an algorithm to serialize and deserialize a binary tree. Serialization converts a tree into a string representation, and deserialization reconstructs the original tree from that string. There is no restriction on how your algorithm works — you just need to ensure that the round-trip produces the same tree structure. For example, root = [1,2,3,null,null,4,5] serializes to "1,2,3,null,null,4,5", and deserializing that string reconstructs the tree. An empty tree serializes to an empty string and deserializes back to null.

Examples

Example 1:
Input: root = [1,2,3,null,null,4,5]
Output: "1,2,3,null,null,4,5"
Explanation: A BFS level-order serialization visits each node from top to bottom, left to right. Missing children are represented as "null". Deserializing reconstructs the same tree by reading values in the same level-order sequence and attaching them to parent nodes using a queue.
Example 2:
Input: root = []
Output: ""
Explanation: An empty tree serializes to an empty string. When deserializing an empty string, the result is null, correctly representing an empty tree.
Constraints:
0 <= nodes <= 10^4
-1000 <= Node.val <= 1000

Algorithm Walkthrough

The core idea behind serializing a binary tree is to convert its entire structure — nodes and their relationships — into a flat string that can be stored or transmitted. The most natural approach is BFS level-order traversal, which processes the tree level by level, left to right. This mirrors the way LeetCode itself represents trees in its input format ([1,2,3,null,null,4,5]), making it intuitive to understand and debug. During serialization, we use a queue to visit each node, writing its value to the output. If a node is null, we write "null". If a node exists, we enqueue both its left and right children (even if they are null) so that their positions in the tree structure are preserved in the output string.

For example, given root = [1,2,3,null,null,4,5], serialization works as follows. We start by enqueueing node 1. We dequeue 1, output "1", and enqueue its children 2 and 3. Next we dequeue 2, output "2", and enqueue its children null and null. Then we dequeue 3, output "3", and enqueue 4 and 5. We dequeue null, output "null". We dequeue null, output "null". Finally we dequeue 4 (output "4") and 5 (output "5"), each enqueueing two nulls. The result is the comma-separated string "1,2,3,null,null,4,5,null,null,null,null". The trailing nulls can optionally be trimmed for compactness, but keeping them simplifies the deserialization logic.

Deserialization reverses the process. We split the serialized string by commas to get a list of node values. The first value becomes the root. We then use a queue to track parent nodes whose children we still need to assign. We maintain an index i into the value list, starting at position 1 (after the root). For each parent dequeued, we assign values[i] as its left child and values[i+1] as its right child. If either value is not "null", we create a TreeNode and enqueue it as a future parent. We increment i by 2 after each parent. This level-by-level reconstruction exactly mirrors the BFS order used during serialization, guaranteeing the correct tree structure is rebuilt.

Why BFS (level-order) works well: Level-order traversal naturally preserves the tree's structure because it records nodes in the same order they would appear reading top-to-bottom, left-to-right. The position of each value in the serialized string directly encodes its parent-child relationship — for any node at index p, its left child is at index 2p+1 and right child at 2p+2 in a complete binary tree. While our queue-based approach doesn't use array indexing directly, the queue ensures children are consumed in the exact same order they were produced. This makes the round-trip deterministic and correct.

An alternative approach is DFS with preorder traversal. In preorder, you visit the root, then recursively serialize the left subtree, then the right subtree. To distinguish between a missing child and the boundary between subtrees, you must insert explicit "null" markers at every null position. For the same tree, a preorder serialization might look like "1,2,null,null,3,4,null,null,5,null,null". Deserialization reads values in preorder, recursively constructing left and right subtrees. While this works, it is more error-prone because forgetting a single null marker shifts all subsequent assignments, corrupting the tree. BFS is generally preferred for its simplicity and alignment with LeetCode's standard format.

Handling edge cases: An empty tree (root is null) serializes to an empty string "". During deserialization, an empty string is detected early and returns null immediately. A single-node tree serializes to just its value, e.g., "42", and deserializes by creating one node with no children. A completely left-skewed tree like [1,2,null,3,null] serializes correctly with nulls on the right, and the queue-based deserializer handles this naturally since it assigns left and right children for each parent independently.

  1. Initialize a queue with the root node (if it exists)
  2. Dequeue each node: if non-null, append its value to the result and enqueue both children; if null, append "null"
  3. Join all values with commas to produce the serialized string
  4. To deserialize, split the string by commas and create the root from the first value
  5. Use a queue of parent nodes and an index to assign left/right children level by level

Idea Map

Start

Key insight: BFS level-order traversal preserves the tree's structural layout

Serialize: Use a queue, output each node's value (or "null"), enqueue children

Join values with commas: "1,2,3,null,null,4,5"

Deserialize: Split by commas, use a queue to assign left and right children level by level

Reconstruct original tree

Complexity Analysis

Time Complexity

O(n) — Both serialize and deserialize visit every node exactly once. Serialization enqueues and dequeues each node, performing O(1) work per node. Deserialization splits the string (O(n) tokens) and processes each token once to create nodes and assign children. The string join/split operations are linear in the total length of the serialized data, which is proportional to n.

Space Complexity

O(n) — The queue in both serialize and deserialize holds at most O(n) nodes (the widest level of the tree). The serialized string itself uses O(n) space to store all node values plus "null" markers and delimiters. The split array during deserialization also uses O(n) space. For a balanced tree the queue uses O(n/2) = O(n) at the bottom level.

Code


class Codec:
Defines the Codec class that provides two methods: serialize to convert a tree to a string, and deserialize to reconstruct the tree from a string.
def serialize(self, root):
Serialize method takes the root of a binary tree and returns a comma-separated string representation using BFS level-order traversal.
if not root:
Edge case: if the tree is empty (root is None), return an empty string. Deserialization will handle this by checking for an empty string input.
return ""
Return empty string for an empty tree, signaling to deserialize that there is no tree to reconstruct.
queue = deque([root])
Initialize a deque with the root node. This queue drives the BFS traversal, processing nodes level by level from top to bottom, left to right.
result = []
List to collect the string representation of each node (including "null" for missing nodes) in BFS order.
while queue:
BFS main loop: process every node in the queue, including null placeholders, until all levels are exhausted.
node = queue.popleft()
Dequeue the next node. This could be a real TreeNode or None (a null placeholder enqueued from a previous step).
if node:
If the node is not null, record its value and enqueue its children (even if they are null) to preserve structure.
result.append(str(node.val))
Append the node's value as a string. This preserves the node's position in the level-order sequence.
queue.append(node.left)
Enqueue the left child. If it's None, it will be output as "null" when dequeued, preserving the tree structure.
queue.append(node.right)
Enqueue the right child. Both children are always enqueued to maintain the correct level-order positions.
else:
If the node is null, record "null" to mark this position. No children are enqueued since a null node has no subtrees.
result.append("null")
Append the "null" marker string, indicating no node exists at this position in the tree structure.
return ",".join(result)
Join all collected values with commas to produce the final serialized string, e.g., "1,2,3,null,null,4,5".
def deserialize(self, data):
Deserialize method takes a comma-separated string and reconstructs the original binary tree using BFS level-order logic.
if not data:
Edge case: if the input string is empty, return None to represent an empty tree.
return None
Return None for empty input, matching the serialization of an empty tree.
nodes = data.split(",")
Split the serialized string by commas into an array of token strings. Each token is either a number or "null".
root = TreeNode(int(nodes[0]))
Create the root TreeNode from the first token. The first value in a level-order serialization is always the root.
queue = deque([root])
Initialize a queue with the root node. This queue holds nodes that need their left and right children assigned.
i = 1
Index into the nodes array, starting at 1 (position 0 is the root). For each parent, nodes[i] is the left child and nodes[i+1] is the right child.
while queue:
Process each parent node in the queue, assigning its left and right children from the serialized token list.
node = queue.popleft()
Dequeue the next parent node that needs children assigned.
if i < len(nodes) and nodes[i] != "null":
Check if there's a valid left child token. If within bounds and not "null", create a TreeNode for the left child.
node.left = TreeNode(int(nodes[i]))
Create and assign the left child node from the token at index i.
queue.append(node.left)
Enqueue the left child so its own children can be assigned later.
i += 1
Advance the index past the left child token.
if i < len(nodes) and nodes[i] != "null":
Check if there's a valid right child token. Same logic as the left child check.
node.right = TreeNode(int(nodes[i]))
Create and assign the right child node from the token at the current index.
queue.append(node.right)
Enqueue the right child so its own children can be assigned later.
i += 1
Advance the index past the right child token. The next iteration will process the next parent.
return root
Return the fully reconstructed tree root. The tree is now identical in structure and values to the original.

public class Codec {
Defines the Codec class providing serialize and deserialize methods for binary tree conversion to/from a string.
public String serialize(TreeNode root) {
Serialize method that takes the root of a binary tree and returns a comma-separated BFS level-order string.
if (root == null) return "";
Edge case: return empty string for a null root, signaling an empty tree during deserialization.
Queue<TreeNode> q = new LinkedList<>();
Initialize a LinkedList-based queue for BFS traversal of the tree.
q.offer(root);
Enqueue the root node to begin BFS traversal.
StringBuilder sb = new StringBuilder();
StringBuilder for efficiently building the comma-separated output string.
while (!q.isEmpty()) {
BFS loop: process every node in the queue until all levels are exhausted.
TreeNode node = q.poll();
Dequeue the next node, which may be a real TreeNode or null.
if (node != null) {
If the node exists, append its value and enqueue both children to preserve the tree structure.
sb.append(node.val).append(",");
Append the node's value followed by a comma to the output string.
q.offer(node.left);
Enqueue the left child (even if null) to preserve its position in the level-order sequence.
q.offer(node.right);
Enqueue the right child (even if null) to preserve its position in the level-order sequence.
} else {
If the node is null, append "null" as a placeholder marker.
sb.append("null,");
Append "null," to mark this position as empty in the serialized string.
return sb.toString();
Convert the StringBuilder to a String and return the complete serialized representation.
public TreeNode deserialize(String data) {
Deserialize method that reconstructs the original binary tree from a comma-separated BFS string.
if (data.isEmpty()) return null;
Edge case: return null for an empty input string, representing an empty tree.
String[] nodes = data.split(",");
Split the serialized string by commas into an array of token strings.
TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));
Create the root TreeNode from the first token (always the root in BFS order).
Queue<TreeNode> q = new LinkedList<>();
Queue of parent nodes that need their left and right children assigned from the token array.
q.offer(root);
Enqueue the root as the first parent whose children need to be assigned.
int i = 1;
Token index starting at 1 (root is at 0). For each parent, tokens i and i+1 are its left and right children.
while (!q.isEmpty()) {
Process each parent in the queue, assigning children from the token array.
TreeNode node = q.poll();
Dequeue the next parent node.
if (i < nodes.length && !nodes[i].equals("null")) {
Check bounds and whether the left child token is a valid number (not "null").
node.left = new TreeNode(Integer.parseInt(nodes[i]));
Create and assign the left child node from the token.
q.offer(node.left);
Enqueue the left child so its children can be assigned in subsequent iterations.
i++;
Advance the index past the left child token.
if (i < nodes.length && !nodes[i].equals("null")) {
Check bounds and whether the right child token is a valid number.
node.right = new TreeNode(Integer.parseInt(nodes[i]));
Create and assign the right child node from the token.
q.offer(node.right);
Enqueue the right child for its own children to be assigned later.
i++;
Advance the index past the right child token.
return root;
Return the fully reconstructed tree root, identical in structure to the original tree.

class Codec {
Defines the Codec class with public serialize and deserialize methods for binary tree conversion.
public:
Public access specifier for the LeetCode-required entry point methods.
string serialize(TreeNode* root) {
Serialize method: converts a binary tree into a comma-separated string using BFS level-order traversal.
if (!root) return "";
Edge case: return empty string for a null root pointer.
queue<TreeNode*> q;
Initialize a queue of TreeNode pointers for BFS traversal.
q.push(root);
Push the root node onto the queue to begin BFS.
string s;
String to accumulate the serialized output.
while (!q.empty()) {
BFS main loop: process every node in the queue.
TreeNode* node = q.front(); q.pop();
Retrieve and remove the front node from the queue.
if (node) {
If the node is not null, append its value and enqueue both children.
s += to_string(node->val) + ",";
Append the node's value followed by a comma to the output string.
q.push(node->left);
Enqueue the left child (even if null) to preserve level-order positions.
q.push(node->right);
Enqueue the right child (even if null) to preserve level-order positions.
} else {
If the node is null, append "null" as a placeholder.
s += "null,";
Append "null," to mark this position as empty in the serialized string.
return s;
Return the complete serialized string.
TreeNode* deserialize(string data) {
Deserialize method: reconstructs the binary tree from a comma-separated BFS string.
if (data.empty()) return nullptr;
Edge case: return nullptr for an empty input string.
vector<string> nodes;
Vector to hold the split token strings from the serialized data.
stringstream ss(data);
Stringstream for tokenizing the comma-separated input string.
string token;
Temporary string to hold each token as it's extracted.
while (getline(ss, token, ',')) nodes.push_back(token);
Split the string by commas, pushing each token into the nodes vector. getline with ',' delimiter extracts one token at a time.
TreeNode* root = new TreeNode(stoi(nodes[0]));
Create the root TreeNode from the first token. stoi converts the string to an integer.
queue<TreeNode*> q;
Queue of parent nodes that need their children assigned from the token list.
q.push(root);
Enqueue the root as the first parent needing children assigned.
int i = 1;
Token index starting at 1 (root at 0). Each parent consumes two tokens for left and right children.
while (!q.empty()) {
Process each parent, assigning left and right children from tokens at index i and i+1.
TreeNode* node = q.front(); q.pop();
Dequeue the next parent node that needs children assigned.
if (i < nodes.size() && nodes[i] != "null") {
Check bounds and whether the left child token is a valid value (not "null").
node->left = new TreeNode(stoi(nodes[i]));
Create and assign the left child node.
q.push(node->left);
Enqueue the left child for its own children to be assigned later.
i++;
Advance index past the left child token.
if (i < nodes.size() && nodes[i] != "null") {
Check bounds and whether the right child token is a valid value.
node->right = new TreeNode(stoi(nodes[i]));
Create and assign the right child node.
q.push(node->right);
Enqueue the right child for its own children to be assigned later.
i++;
Advance index past the right child token.
return root;
Return the fully reconstructed tree root, identical in structure to the original.

Edge Cases & Common Mistakes

⚠️

Leading/trailing commas can cause empty string splits — handle empty input first

If your serialized string has a trailing comma (e.g., "1,2,3,"), splitting by commas produces an empty string as the last element. Attempting to parse this empty string as an integer will throw an error. Always check for empty input at the start of deserialization, and ensure your serialization doesn't produce ambiguous trailing delimiters. Alternatively, strip trailing commas before splitting or skip empty tokens during iteration.

💡

BFS approach naturally handles structure; DFS needs explicit null markers for every position

A key advantage of BFS level-order serialization is that it inherently preserves the tree's shape — each node's children are recorded right after their parent. With DFS (preorder), you must insert null markers for every null child, including leaf children. Missing even one null marker in DFS corrupts the entire deserialization because the recursive structure relies on exact token counts. BFS is more forgiving and easier to debug.

The comma-separated string format with "null" markers is clean, debuggable, and standard

This format matches LeetCode's own tree representation, making it easy to copy serialized output directly into LeetCode test cases for debugging. The string is human-readable, so you can visually verify the tree structure. It's also straightforward to convert to other formats (JSON, arrays) if needed. This standard approach is widely accepted in interviews and production code alike.

Understanding Tree Serialization

Tree serialization is a fundamental concept that appears throughout computer science, from database storage and network transmission to algorithm design and interview problems. Understanding how to convert tree structures into flat representations and back is essential for working with hierarchical data in any context — including JSON, XML, file systems, and compiler abstract syntax trees.

Learn more about understanding tree serialization and deserialization techniques →
lightbulb

Disclaimer: This problem is for educational purposes. Practice the 'Serialize and Deserialize Binary Tree' problem on LeetCode.