Tree serialization
Turn a tree into a flat string, with markers for missing children, so it can be saved and rebuilt exactly.
O(n)Space O(n)4 practice problemsStep-through pictureThe idea
Serializing means turning a structure in memory into text, so you can store it or send it over the network. Deserializing turns the text back into the same structure. JSON.stringify and JSON.parse do this for plain objects.
For a tree, the values alone are not enough, because many shapes have the same values. The fix is to also write down where children are missing. Here we write a preorder walk (node, then left, then right), and write # every time we reach a null child.
With those markers, the text describes exactly one tree. To read it back, take the items in the same order: each item is either # (an empty spot) or a value, followed by its left subtree and then its right subtree.
An everyday example
Packing a mobile into a box
You take down a hanging mobile to ship it. You write instructions as you unhook pieces: "star, then on its left a moon, and the moon's left hook is empty, its right hook is empty, then on the star's right ...". You write "empty" for every unused hook. The person receiving it reads your list in the same order and hangs every piece on the right hook.
| In the example | In the code |
|---|---|
| Writing down a piece | parts.push(String(node.val)) |
| Writing "empty" for an unused hook | parts.push("#") |
| The instruction sheet | parts.join(",") |
| Reading the next instruction | parts[next], then next++ |
| Hanging a piece and then its hooks | node.left = read(); node.right = read(); |
From the slow way to tree serialization
- You want to save a tree as text and load it back later. Writing just the values in preorder fails:
6with a left child1and6with a right child1both come out as6,1. The values alone drop the shape. - What's missing is where each subtree ends. A tree with
nnodes has exactlyn + 1empty child slots. If you write a marker,#, for each of them, a reader can tell "this subtree stops here" without any other hint. - Now the text follows a simple pattern: a subtree is either
#, or a value followed by two subtrees. Writing it is a preorder DFS that writes#at everynull. Reading it is the same DFS run on the text: take the next item, returnnullif it's#, and otherwise make a node and read its left and right subtrees. - The writer and the reader share one thing, the order of the items, and nothing else: no lengths, no brackets, no positions. A preorder walk that writes markers for empty spots, plus a reader that takes items in the same order with a moving index
next, is tree serialization.
How it works
- To serialize, walk the tree in preorder with
write(node). - At a
nullchild, push#intopartsand return. - Otherwise push the node's value, then
write(node.left)andwrite(node.right). - Join
partswith commas. - To deserialize, split the text on commas and read the parts with a moving index
next. read()takes the next part.#meansnull. Otherwise make a node, thenread()its left child andread()its right child, in that order.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function serialize(root) {const parts = [];function write(node) {if (node === null) {parts.push("#");return;}parts.push(String(node.val));write(node.left);write(node.right);}write(root);return parts.join(",");}function deserialize(text) {const parts = text.split(",");let next = 0;function read() {const part = parts[next];next++;if (part === "#") return null;const node = new TreeNode(Number(part));node.left = read();node.right = read();return node;}return read();}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(n) |
Every node and every empty spot is written and read once, and there are 2n + 1 of them. The text is O(n) long, and the recursion goes O(h) deep.
- Best case
O(n)Any tree: every node and every empty slot must be written and read,
2n + 1items in all. - Average case
O(n)A random tree of
nnodes:nvalues andn + 1markers. - Worst case
O(n)A chain of
nnodes: the same2n + 1items, but the recursion goesndeep in both directions.
Common mistakes
- Leaving out the markers for missing children.
1,2,3could be several different trees, so it can't be read back. - Reading the right subtree before the left one. The reader must use the same order as the writer.
- Forgetting to turn the text back into numbers with
Number(part). The tree then holds strings like"4". - Using a separator that can appear inside values, like a comma inside a label. Pick a safe separator or escape the values.
When to use it
Look for these clues in a problem:
- You must save a tree to a string (local storage, a URL, a file) and rebuild it later.
- You need to compare subtrees by shape and values, for example to find duplicates.
- The question gives a text format for nested data and asks you to parse it.
- A tree must be sent between a worker, a server, and the page.
Check yourself
Answer each one in your head first, then open it.
How many items does the text have for a tree with n nodes?
2n + 1: n values and n + 1 markers.
Why doesn't read need to know where the left subtree ends before reading the right one?
Reading the left subtree uses up exactly its items and leaves next at the start of the right subtree, so the right call just continues from there.
What does serialize write for a single node holding 7?
7,#,#: the value followed by two markers for its empty children.
Why does preorder without markers fail for general trees?
Different shapes can give the same values in the same order, so the reader has no way to know where a subtree ends.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 4 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The text pattern: a subtree's text is # when it's empty, or value,left,right otherwise, where left and right are subtree texts too. write produces exactly this, because it writes # at null and value, then left, then right for a real node.
The rule for read: when it's called, parts[next] is the first item of some subtree's text, and when it returns, next points just past the end of that text. For # it reads one item, which is the whole text of an empty subtree. For a value it reads one item and then, by the same rule, the whole left text and the whole right text, so it stops exactly at the end.
Because read takes items in the same order write produced them, each value lands in the same place it came from, so the rebuilt tree has the same values and the same shape. It ends because every call uses at least one item, and there are 2n + 1 items.
Going deeper
3 more ideas
Why the markers make the text exact
With a # for every missing child, each node in the text is followed by exactly two descriptions: its left subtree and its right subtree. A description is either a single # or a value followed by its own two descriptions. So the reader always knows where one subtree ends, and there is only one way to read the text.
Level order works too
The format used by tests on this site writes the tree level by level with null for gaps, like [1, 2, 3, null, null, 4, 5]. It is read with a queue: each node in the queue takes the next two items as its children. Both formats work; preorder with markers is the simplest to write recursively.
Time and memory
Both directions handle every node and every missing child once. A tree with n nodes has n + 1 missing children, so the text has 2n + 1 items and the work is O(n). Memory is O(n) for the text plus O(h) for the recursion.
Work it by hand
Every step in a table, with why
Read back the text written for a 4-node tree. Every item is shown, so you can see each # close a subtree.
text = "6,1,#,3,#,#,9,#,#" (written by `serialize` for the tree `[6, 1, 9, null, 3]`)
| Step | next | part | Result | What happens | Why |
|---|---|---|---|---|---|
| 1 | 0 | 6 | new node 6 | read its left subtree | The first item is always the root. Before returning, 6 must read its left subtree and then its right one. |
| 2 | 1 | 1 | new node 1 | becomes 6.left | The left subtree of 6 starts here. 1 now reads its own two subtrees. |
| 3 | 2 | # | null | 1.left = null | A marker: 1 has no left child. This call used exactly one item. |
| 4 | 3 | 3 | new node 3 | becomes 1.right | The right subtree of 1 starts with 3. |
| 5 | 4 | # | null | 3.left = null | 3 has no left child. |
| 6 | 5 | # | null | 3.right = null | 3 has no right child, so 3 is done, which finishes 1's right side, so 1 is done too. |
| 7 | 6 | 9 | new node 9 | becomes 6.right | next now points just past 6's whole left subtree, so this item starts the right subtree. |
| 8 | 7 | # | null | 9.left = null | 9 has no left child. |
| 9 | 8 | # | null | 9.right = null | 9 is done, so 6 is done, and the first call returns the finished tree. |
The tree [6, 1, 9, null, 3], the same one that was written. The text had 9 items, 2 · 4 + 1 for 4 nodes, and next ends at 9, exactly the number of items, so nothing was skipped or left over.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Tree serialization | O(n) | O(n) | You need a compact string that reads back into exactly the same tree. |
Level order with null gaps | O(n) | O(n) | Trees may be very deep, or you want the format often used in tests and examples. |
JSON.stringify of the nodes | O(n) | O(n) | Quick saving of small plain trees, where size and depth don't matter. |
Extra memory: O(n) for the text and the parts array, plus O(h) for the recursion: about log₂ n calls deep for a balanced tree and n for a chain.
How the steps grow
- Tree serialization, Level order with
nullgaps,JSON.stringifyof the nodesO(n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Tree serialization | 10 | 1,000 | 1 million |
Level order with null gaps | 10 | 1,000 | 1 million |
JSON.stringify of the nodes | 10 | 1,000 | 1 million |
Inputs that need care
5 tricky inputs
- An empty tree:
root = null serializereturns"#", anddeserialize("#")reads that one item and returnsnull.- Negative and decimal values, like
-4or2.5 String(-4)is"-4"andNumber("-4")is-4, so they come back unchanged. Commas are safe, because the text of a number never contains one.- String values that contain a comma or are
"#" split(",")would cut such a value in two, and a value"#"would read as an empty spot. Escape these, or encode each value withJSON.stringify.- The value
-0 String(-0)is"0", so-0comes back as0. That's usually harmless; if it matters, write a special marker for it.- A chain 30,000 nodes deep
- Both functions recurse 30,000 calls deep and may overflow the stack. Use the level-order format with a queue, which needs no recursion.
Variations and follow-ups
Twists interviewers add
Level-order format
Write the tree level by level with a queue, null for gaps, and drop the trailing nulls, as in [6, 1, 9, null, 3]. Reading uses a queue too: each node takes the next two items as its children. It never recurses, so depth is no problem.
Search trees without markers
A BST's preorder alone is enough, because the order rule decides where each value goes. Read it back with bounds, as when building a BST from preorder. The text is about half as long.
Trees with many children
Write each node's value followed by its number of children, like 1,3,..., then each child's text. The count does the job of the markers.
JSON
JSON.stringify(root) works directly for small trees of plain objects, since null children become null. The text is larger, the work is recursive, and it throws on cycles, such as nodes that also store a parent pointer.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "serialize", "encode and decode", "save and restore a tree", or "send a tree over the network".
- Show why the markers matter with two tiny trees:
6with a left child1writes6,1,#,#,#, while6with a right child1writes6,#,1,#,#. - Say that the writer and the reader must walk in the same order, and point at
nextas the only shared state the reader needs. - Expect "can you make it shorter?". For a BST, drop the markers and use preorder alone. For other trees, drop the trailing markers in the level-order form.
Where frontend code uses it
The same idea in real UI code
- Saving an editor's split-pane layout or a tree view's open folders to
localStoragemeans serializing a tree and parsing it back on the next visit. element.outerHTMLserializes a DOM subtree into HTML text, andDOMParserturns HTML text back into a DOM tree.- Server-side rendering turns a component tree into an HTML string on the server, and the browser parses it back into the DOM.