Algorithm

Binary Tree Traversals (DFS)

Trees Pattern

Binary Tree Traversals (DFS)

Given the root of a binary tree, return every node's value in a single flat array, listed in the order the traversal visits them. Three orders are asked for. Pre-order records a node before either of its children (node, then left subtree, then right subtree). In-order records a node between them (left subtree, then node, then right subtree). Post-order records a node after both (left subtree, then right subtree, then node). If the tree is empty (root is null), return an empty array.

CONSTRAINTS
  • The number of nodes in the tree is in the range [0, 100]
  • -100 <= Node.val <= 100
  • Return the values as a flat array, in visit order
  • Node values are not necessarily distinct
EXAMPLE 1
Input: root = [1,2,3,4,5] — 1 has children 2 and 3; 2 has children 4 and 5
Output: Pre-order: [1,2,4,5,3] | In-order: [4,2,5,1,3] | Post-order: [4,5,2,3,1]
Same tree, same set of numbers, three different orderings. Every node still appears exactly once in each list — only the moment at which a node gets written down changes.
EXAMPLE 2
Input: root = [1,null,2,3] — 1 has no left child; its right child is 2; 2's left child is 3
Output: Pre-order: [1,2,3] | In-order: [1,3,2] | Post-order: [3,2,1]
A missing child is simply skipped, it is not a node. In-order puts 1 first because 1 has nothing on its left at all, so its 'left subtree' contributes nothing before it.
EXAMPLE 3
Input: root = [5]
Output: Pre-order: [5] | In-order: [5] | Post-order: [5]
With a single node there are no children to come before or after it, so all three orders collapse to the same one-element list.
EXAMPLE 4
Input: root = []
Output: Pre-order: [] | In-order: [] | Post-order: []
There are no nodes, so nothing can be recorded. An empty tree is not an error case, it is a legitimate tree with zero nodes.
What should be returned if the tree is empty?
An empty array. A null root means zero nodes to visit, so every order produces an empty list.
Is this a binary search tree, or just any binary tree?
Any binary tree — do not assume the values are ordered in any way. That matters, because in-order only produces sorted output on a binary search tree, and nothing here promises that.
Can values repeat?
Yes. Nothing forbids two nodes holding the same number, so you cannot identify a node by its value alone.
Am I allowed to use recursion, or must it be iterative?
Recursion is fine here and is the natural fit. Worth asking, though, because the tree could in principle be one long chain, and a very deep chain can exhaust the language's call stack — that concern is what the follow-up, Iterative Traversals, is about.

An array lays its items out in a line, so "go through all of them" has one obvious meaning: start at the left end, walk to the right end. A tree has no line. From a node you can go two ways at once, so "go through all of them" needs a decision, and different decisions produce different orderings of the very same nodes. This page is about that decision — and it is worth taking slowly, because almost every tree problem you will ever see is really the question "in what order should I visit, and at what moment should I do my work?" in disguise.

What a binary tree actually is

A node is a small box holding a value plus two links, one called left and one called right. Each link either points at another node or points at nothing (null). The whole structure is reached through one entry point, the root. A node with both links empty is a leaf — the end of a branch. Nodes are never allowed to link back upward or sideways, so there are no loops: from the root, every node is reachable by exactly one downward path.

Here is the observation everything else grows from. Take any node and look at what hangs below its left link. It is a node with a value and two links of its own, reachable through a single entry point — that is, it is itself a binary tree, just a smaller one. We call it the node's left subtree. The same holds on the right. So a binary tree is not merely "a bunch of nodes"; it is a value with two smaller binary trees attached. A structure defined in terms of smaller copies of itself is called self-similar, and self-similar structures are exactly what recursion was invented for.

Recursion, without the mystery

Recursion just means a function that calls itself on a smaller piece of the input. Two parts are needed, and only two:

- A base case: a piece so small the answer needs no further work. For trees this is almost always "the node is null" — an empty tree has nothing to visit, so return immediately.
- A recursive step: assume the function already works on the smaller pieces, use its results, done.

That second part is where people get stuck, so say it plainly: when you write traverse(node.left), you do not trace what happens inside. You assume it correctly handles the entire left subtree, however big it is, and you move on. This is not hand-waving, it is an induction argument. The base case is correct on its own. Every larger case is correct provided the smaller ones are. Since every call moves strictly downward and the tree is finite, every chain of calls reaches null eventually — so correctness propagates from the leaves back up to the root. Write the base case, write the one honest step, trust the rest.

Underneath, the machine is doing the bookkeeping you refused to do. Each call gets a stack frame — a scrap of memory holding that call's own node and a bookmark for which line it had reached. Calls pile up as you descend and are removed as they finish, so the frames behave like a stack of plates: the last one added is the first one taken off. When a call finishes, the machine pops its frame and resumes the parent exactly where it paused. That automatic pause-and-resume is why "go all the way down the left, then come back up and handle the right" needs no explicit machinery from you.

One skeleton, three moments

Now write the only traversal there is. Visit the left subtree, visit the right subtree, and somewhere among those two calls, do your own work:

python
def traverse(node):
    if node is None:      # base case: empty tree, nothing to do
        return
    # (A) work here — before both children
    traverse(node.left)
    # (B) work here — between the children
    traverse(node.right)
    # (C) work here — after both children

The recursive calls never move. The only freedom in the entire structure is which of the three slots holds your work, and each choice has a name:

- Work in slot (A) — the node is recorded before anything below it. This is pre-order (node, left, right).
- Work in slot (B) — the whole left subtree is finished first, then the node, then the right. This is in-order (left, node, right).
- Work in slot (C) — the node is recorded only after both subtrees are completely done. This is post-order (left, right, node).

That is the entire content of those three intimidating words. Not three algorithms — one algorithm, three positions for a single line.

Key Insightchoose the slot by asking when your node has what it needs. If a node's job depends on information coming from above (its depth, the path taken to reach it, a value inherited from its parent), the node can act the moment it arrives — that is pre-order, information flowing downward. If a node's job depends on answers from below (how tall its subtrees are, what they summed to, whether they are valid), it cannot possibly act on arrival; it must wait for both children to report back — that is post-order, information flowing upward. In-order is the odd one out: its position between the children is only interesting when left-versus-right encodes an ordering, which is precisely the case in a binary search tree.
Worked example:the tree with root 1, children 2 and 3, and 2's children 4 and 5
Follow pre-order, recording at arrival. We arrive at 1 and write it. Descend left to 2, write it. Descend left to 4, write it; both of 4's links are empty, so its two calls hit the base case and return at once. Back up in 2, now go right to 5, write it, its children are empty, return. 2 is finished, so we resume 1, which now goes right to 3, writes it, and finishes. Result: 1, 2, 4, 5, 3.

Same walk, but record in slot (B) instead. At 1 we do not write yet — we descend left first. At 2, still nothing — descend left again. At 4, its left is empty and returns immediately, so 4 writes itself, then its empty right returns. Back in 2, the left subtree is now done, so 2 writes itself, then descends right to 5, which writes itself. Back in 1: left subtree done, so 1 writes itself, then 3 writes itself. Result: 4, 2, 5, 1, 3 — every node sandwiched between its left descendants and its right descendants.

And slot (C): 4 writes after both its empty children, then 5, then 2 (only once 4 and 5 are done), then 3, then finally 1 — the root last, always. Result: 4, 5, 2, 3, 1. Notice pre-order always starts with the root and post-order always ends with it. That is not a coincidence to memorize, it falls straight out of the slot you picked.

What it costs

Each node is entered exactly once and does a constant amount of work there — a null check, a record, two calls — so the time is O(N) for N nodes: double the tree, double the work. You cannot beat that, since listing every value requires touching every value.

Memory is the more interesting number. At any instant, the frames alive on the stack are exactly the nodes on the path from the root down to where you currently stand, so the peak is the tree's height H: O(H) extra space. For a bushy, balanced tree the height is about log₂N — 20 frames for a million nodes, nothing. But nothing forces a tree to be bushy. A tree where every node has only a left child is one long chain of N nodes; then H = N and the stack holds N frames at once. That worst case is why the very next problem, Iterative Traversals, replaces the hidden call stack with one you build and control yourself.

Train the reflex here, because it pays for the rest of this section: when a tree problem lands, do not ask "which traversal is this?" Ask "what does one node need in order to do its job, and where does that information come from?" Needs something from above → act on the way down, pre-order, and pass the information into the recursive calls as arguments (Path Sum, Binary Tree Paths). Needs something from below → let the calls return values to you and combine them on the way up, post-order (Maximum Depth, Diameter, Balanced Binary Tree). Answer that one question and the code writes itself.

Interactive Strategy Visualization
Recursive Trace
ABCDE
A
B
D
E
C

Strategic Note

DFS explores branches deep before moving wide. Visit node, then recurse left and right. Best for cloning trees.

Complexity
Time: O(N) | Space: O(H)

Strategy

Focus on the recursive nature of trees: solve for subtrees and combine results at the root.

"Divide and Conquer: Subproblem → Recurrence → Result"

O(N) Time · O(H) Space Recursive
O(N) Time · O(H) Space Explicit Stack
O(N) Time · O(1) Space Morris