DSA sheet · Trees · DFS pattern

Boundary Traversal

Walk around the outside edge of a tree, anticlockwise: down the left edge, along the bottom (all the leaves), then up the right edge. The teacher calls this a very important interview question. Her approach breaks the big task into three small helpers: one collects the left edge, one collects the leaves, one collects the right edge (in reverse). The root is handled on its own first, so it isn't counted twice. Each helper is simple. The real learning is in the small conditions: why leaves are skipped on the edges, why we go right only when there's no left, and why the right edge must be reversed.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the conditions from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Words we will use

wordmeaning
leafA node with no children (left is None and right is None).
non-leaf (internal node)A node with at least one child.
left boundaryThe nodes on the left edge, going down from the root's left child, without the leaves.
right boundaryThe nodes on the right edge, going down from the root's right child, without the leaves.
anticlockwiseThe direction we walk around the tree: down the left side, left-to-right along the bottom, then up the right side.

The teacher's tree

             1              ← root
           /   \
          2     3           ← 2 is on the left edge, 3 on the right edge
         / \   / \
        4   5 6   7         ← 4, 6, 7 are leaves
           / \
          8   9             ← 8, 9 are leaves

Answer: 1 2 4 8 9 6 7 3. Trace it with your finger: down the left, along the bottom, up the right.

        start → 1
               ↙   ↖
              2     3         ← going up the right side at the end
             ↓       ↑
            4 → 8 → 9 → 6 → 7   (bottom row, left to right)

The plan: three helpers + the root

The teacher's first thought is "one function for the left side, one for the leaves, one for the right side". Then she notices a problem: the root sits at the top of both the left edge and the right edge, so it would be added twice. Her fix: handle the root separately, then start the left helper from root.left and the right helper from root.right.

  1. If the root is not a leaf, add the root.
  2. Add the left boundary, starting at root.left.
  3. Add all leaves, searching from root.
  4. Add the right boundary, starting at root.right, reversed.

All three helpers use one tiny check:

is_leaf helper
def is_leaf(node):
    return node.left is None and node.right is None

Part A · Piece 1: the left boundary

GFG · Boundary Traversal of Binary Tree

1The question in simple words

Starting from the root's left child, walk down the left edge of the tree and collect every node on it that is not a leaf, top to bottom.

2What the constraints tell us

3Intuition: what can you see from the left?

The left edge is the path you get by always stepping left. But if a node has no left child, its right child is now the outermost node on that level, so it becomes part of the edge. In the teacher's words: if the left is there, the right is hidden behind it. If the left is missing, the right becomes visible.

So we walk down one path, choosing left whenever we can, and right only when we must. We add each node we pass, unless it's a leaf.

4Building the conditions from examples

Condition 1: skip leaves

On the teacher's tree the walk is 2 → 4. 2 is not a leaf → add. 4 is a leaf → don't add.

Doubt: 4 is clearly on the left edge. Why not add it here?
→ Because the leaf helper (Part B) will add every leaf, including 4. If the left boundary added it too, 4 would appear twice. Each piece owns its own nodes: edges own non-leaves, the leaf helper owns leaves. The problem statement says the same thing: the left boundary is the non-leaf nodes.

Condition 2: go left if possible, else go right

The teacher changes the tree: remove 4.

             1
           /   \
          2     3
           \   / \
            5 6   7
           / \
          8   9

Now 2 has no left child. Looking from the left, you'd see 2, then 5, then 8. So the walk is 2 → 5 → 8: at 2 we must step right, because there is no left.

Left boundary = 2, 5, and the full answer for this tree becomes 1 2 5 8 9 6 7 3.

The move ruleif node.left: node = node.left
else: node = node.right
Never visit both children. We only follow the edge.

Condition 3: stop when we fall off the tree

The loop runs while node is not None. Stepping past a leaf gives None, and that ends the walk. The same check covers the case where the root has no left child at all: the loop never starts and the left boundary is empty.

Doubt: the teacher says she does this part "with BFS". Is it really BFS?
→ Not quite. There's no queue and no level-by-level visiting. It's just a while loop that walks down one path (an iterative walk). She means "with a loop instead of recursion". She also says a recursive version works just as well: add the node, then recurse into left (or right if there's no left).

5Approach steps

  1. Start with node = root.left.
  2. While node is not None:
  3. If it's not a leaf, add node.val.
  4. If it has a left child, move left. Otherwise move right.

6Code (Python)

left_boundary helper
def left_boundary(node, ans):
    while node:                         # stop when we fall off the tree
        if not is_leaf(node):           # leaves are added by leaf_nodes
            ans.append(node.val)
        if node.left:                   # left is the outer side
            node = node.left
        else:                           # no left -> right becomes the edge
            node = node.right

7Code line by line

linewhat it means
while node:Keep walking until we step past the bottom (or the start is already None).
if not is_leaf(node): ans.append(node.val)Only non-leaf edge nodes belong here. Leaves are added later, exactly once.
if node.left: node = node.leftPrefer left: it's the outermost node on the next level.
else: node = node.rightNo left child, so the right child is the one visible from the left side.

8Dry run

treenodeleaf?addednext step
teacher's tree2no2has left → 4
4yes—no left → right → None → stop
without 42no2no left → right → 5
5no5has left → 8
8yes—None → stop

9Complexity & remember

Remember the left boundaryStart at root.left. Add if not a leaf. Go left if you can, right if you must.

Part B · Piece 2: the leaf nodes

1The question in simple words

Collect every leaf of the whole tree, in order from left to right.

2What the constraints tell us

3Intuition

DFS that always goes left before right visits leaves exactly in left-to-right order. So: visit every node; if it's a leaf, write it down; otherwise go into its left child, then its right child.

Why we pass the root here (not root.left)

Leaves can be on both sides of the root, so we search the whole tree from root. And if the root itself is a leaf (a one-node tree), this is the helper that adds it.

4Conditions

Doubt: could I find the leaves with BFS instead?
→ BFS gives leaves level by level, not left to right along the bottom. In the teacher's tree, BFS would give 4, 6, 7 (level 2) before 8, 9 (level 3). The boundary needs 4, 8, 9, 6, 7. That's why this piece uses DFS.

5Approach steps

  1. If the node is None, return.
  2. If it's a leaf, add it and return.
  3. Recurse left, then recurse right.

6Code (Python)

leaf_nodes helper
def leaf_nodes(node, ans):
    if node is None:                    # base case
        return
    if is_leaf(node):
        ans.append(node.val)
        return
    leaf_nodes(node.left, ans)          # left first -> left-to-right order
    leaf_nodes(node.right, ans)

7Code line by line

linewhat it means
if node is None: returnStops the recursion at missing children.
if is_leaf(node): ans.append(node.val) returnA leaf is part of the bottom edge. It has no children, so we're done here.
leaf_nodes(node.left, ans) leaf_nodes(node.right, ans)Search the left subtree fully, then the right one.

8Dry run with the call stack

  1. 1: not a leaf → go left.
  2. 2: not a leaf → go left.
  3. 4: leaf → add 4. Return to 2.
  4. 2 goes right: 5: not a leaf → go left: 8: leaf → add 8. Back to 5, go right: 9: leaf → add 9.
  5. 5 returns, 2 returns. 1 goes right: 3: not a leaf → left 6: leaf → add 6. Right 7: leaf → add 7.
  6. Everything returns. Leaves = 4, 8, 9, 6, 7.
when 8 is added
1258 → add
after 8 returns, 9 is visited
1259 → add
right side
136 → add

The stack never holds more than one node per level at a time: 1, 2, 5, 8. Only after 8 is removed does 9 go on.

9Complexity & remember

Remember the leavesPlain DFS from the root, left before right. Add a node only if it has no children.

Part C · Piece 3: the right boundary (reversed)

1The question in simple words

Starting from the root's right child, walk down the right edge and collect every non-leaf node. But the boundary goes up the right side, so these nodes must appear bottom to top in the answer.

2What the constraints tell us

3Intuition: mirror of the left boundary, plus a flip

It's the left-boundary walk seen in a mirror: prefer right, and go left only if there's no right child. The walk naturally goes top to bottom, but we need bottom to top. So we collect the nodes in a stack first, then pop them into the answer. Popping a stack gives the last item first, which reverses the order.

4Building the conditions from examples

Why we need the reverse: the teacher's extra example

On the teacher's tree, the right walk is 3 → 7, and 7 is a leaf, so only 3 is collected. With one node, you can't tell whether the order needs reversing. So she adds a child under 7 (we call it 10):

             1
           /   \
          2     3
         / \   / \
        4   5 6   7
           / \     \
          8   9     10

So the right boundary must be added reversed: 7, 3. The full answer for this tree: 1 2 4 8 9 6 10 7 3.

The move rule is mirrored

Right boundary move ruleif node.right: node = node.right
else: node = node.left
Right is the outer side now. Left only if right is missing.
Doubt: do I have to use a stack?
→ No. The teacher says a temporary list that you reverse works the same. In Python, a plain list is a stack: append pushes and pop() takes from the end. Adding reversed(temp) is the same idea in one line.
Doubt: why start at root.right and not root?
→ The root was already added in the main function. Starting from root would add it a second time at the very end.

5Approach steps

  1. Start with node = root.right and an empty stack.
  2. While node: if it's not a leaf, push its value. Move right if possible, else left.
  3. While the stack isn't empty: pop and add to the answer.

6Code (Python)

right_boundary helper
def right_boundary(node, ans):
    stack = []                          # collects top-to-bottom
    while node:
        if not is_leaf(node):
            stack.append(node.val)
        if node.right:                  # right is the outer side here
            node = node.right
        else:
            node = node.left
    while stack:                        # pop = bottom-to-top
        ans.append(stack.pop())

7Code line by line

linewhat it means
stack = []Temporary holder, so we can flip the order at the end.
if not is_leaf(node): stack.append(node.val)Same rule as the left side: leaves belong to the leaf helper.
if node.right: node = node.right else: node = node.leftMirror of the left walk: right first, left only when right is missing.
while stack: ans.append(stack.pop())The last pushed (deepest) comes out first → bottom to top.

8Dry run (the tree with 10 under 7)

  1. node = 3: not a leaf → push 3. Has right → go to 7. stack: [3]
  2. node = 7: not a leaf (it has 10) → push 7. Has right → go to 10. stack: [3, 7]
  3. node = 10: leaf → skip. No right → go left → None. Loop ends.
  4. Pop 7 → answer gets 7. Pop 3 → answer gets 3. Right boundary added: 7, 3 ✓
stack after the walk
37
pop order
3 (second)7 (first)

9Complexity & remember

Remember the right boundaryStart at root.right. Skip leaves. Right if you can, left if you must. Push to a stack, then pop, so it comes out bottom to top.

Part D · Putting it together

1The question in simple words

Return root, then left boundary, then leaves, then the reversed right boundary, with every boundary node appearing exactly once.

2What the constraints tell us

3Intuition: who owns which node

pieceownsstarts at
main functionthe root (if it isn't a leaf)root
left_boundarynon-leaf nodes on the left edgeroot.left
leaf_nodesall leavesroot
right_boundarynon-leaf nodes on the right edge, reversedroot.right

No node is owned twice, so no node is printed twice.

4The two root conditions

Why "add the root only if it's not a leaf"?

Take a tree with just one node, 1. It is the root and a leaf. If the main function added it, and then leaf_nodes(root) found it as a leaf, we'd get [1, 1] ✗. So the main function skips a leaf root and lets the leaf helper add it → [1] ✓. Both boundary helpers get None and add nothing.

Why the order of the calls matters

The answer is built by appending, so the calls must run in the anticlockwise order: left boundary → leaves → right boundary. Calling the right helper before the leaves would put 3 before 4.

Doubt: what if the root has no left child at all?
→ left_boundary(None) adds nothing, so the answer goes root, then straight to the leaves. E.g. the chain 1 → right 2 → right 3 gives [1, 3, 2]: root 1, leaf 3, then the right edge (2) going up. This matches the usual GFG convention.

5Approach steps

  1. If root is None, return [].
  2. If root is not a leaf, add root.val.
  3. left_boundary(root.left, ans)
  4. leaf_nodes(root, ans)
  5. right_boundary(root.right, ans)
  6. Return ans.

6Code (Python), the full solution

Boundary Traversal: full solution
def is_leaf(node):
    return node.left is None and node.right is None

def left_boundary(node, ans):
    while node:
        if not is_leaf(node):
            ans.append(node.val)
        if node.left:
            node = node.left
        else:
            node = node.right

def leaf_nodes(node, ans):
    if node is None:
        return
    if is_leaf(node):
        ans.append(node.val)
        return
    leaf_nodes(node.left, ans)
    leaf_nodes(node.right, ans)

def right_boundary(node, ans):
    stack = []
    while node:
        if not is_leaf(node):
            stack.append(node.val)
        if node.right:
            node = node.right
        else:
            node = node.left
    while stack:
        ans.append(stack.pop())

def boundaryTraversal(root):
    ans = []
    if root is None:
        return ans
    if not is_leaf(root):               # root once, unless it is a leaf
        ans.append(root.val)
    left_boundary(root.left, ans)       # 1. down the left edge
    leaf_nodes(root, ans)               # 2. along the bottom
    right_boundary(root.right, ans)     # 3. up the right edge
    return ans

7Code line by line (main function)

linewhat it means
if root is None: return ansEmpty tree → empty answer.
if not is_leaf(root): ans.append(root.val)The root is shared by both edges, so it's added once, here. If it's a leaf, the leaf helper adds it instead.
left_boundary(root.left, ans)Start below the root so the root isn't repeated.
leaf_nodes(root, ans)Search the whole tree for leaves, left to right.
right_boundary(root.right, ans)Start below the root. The helper reverses its nodes.

8Dry run on the teacher's tree

             1
           /   \
          2     3
         / \   / \
        4   5 6   7
           / \
          8   9
  1. Root 1 is not a leaf → ans = [1].
  2. Left boundary from 2: 2 added; 4 is a leaf, skipped → ans = [1, 2].
  3. Leaves from 1: 4, 8, 9, 6, 7 → ans = [1, 2, 4, 8, 9, 6, 7].
  4. Right boundary from 3: stack [3]; 7 is a leaf, skipped → pop 3 → ans = [1, 2, 4, 8, 9, 6, 7, 3].
  5. Final: 1 2 4 8 9 6 7 3 ✓
treerootleftleavesright (reversed)answer
teacher's124 8 9 6 731 2 4 8 9 6 7 3
without 412 58 9 6 731 2 5 8 9 6 7 3
10 under 7124 8 9 6 107 31 2 4 8 9 6 10 7 3
single node 1— (leaf)—1—1

9Complexity & remember

Remember the whole thingRoot (if not leaf) → left edge from root.left (no leaves) → all leaves by DFS (left first) → right edge from root.right (no leaves) reversed.

Part E · Revision page

left_boundaryleaf_nodesright_boundary
starts atroot.leftrootroot.right
stylewhile loop down one pathrecursive DFSwhile loop down one path + stack
addsnon-leavesleaves onlynon-leaves
movesleft, else rightleft subtree, then right subtreeright, else left
order in answertop → bottomleft → rightbottom → top (reversed)
timeO(h)O(n)O(h)
If you remember only 5 lines 1. Boundary = root + left edge + leaves + right edge reversed (anticlockwise).
2. Add the root separately, and only if it isn't a leaf.
3. Edges skip leaves. The leaf helper adds every leaf once.
4. Left edge: left if possible, else right. Right edge: right if possible, else left.
5. Right edge goes into a stack, so it comes out bottom to top. Total O(n) time, O(h) space.
Mistakes to avoid ✗ starting the edges from root (root printed two or three times)
✗ adding leaves in the edge helpers (4 and 7 printed twice)
✗ visiting both children in the edge walk (inner nodes like 5 sneak in)
✗ forgetting to reverse the right edge (3, 7 instead of 7, 3)
✗ collecting leaves with BFS (wrong order: 4 6 7 8 9)
✗ adding a leaf root in the main function ([1, 1] for a single node)
test it yourself (paste under the full solution above)
t = TreeNode(1,
      TreeNode(2, TreeNode(4), TreeNode(5, TreeNode(8), TreeNode(9))),
      TreeNode(3, TreeNode(6), TreeNode(7)))
no4 = TreeNode(1,
        TreeNode(2, None, TreeNode(5, TreeNode(8), TreeNode(9))),
        TreeNode(3, TreeNode(6), TreeNode(7)))
print(boundaryTraversal(t))            # [1, 2, 4, 8, 9, 6, 7, 3]
print(boundaryTraversal(no4))          # [1, 2, 5, 8, 9, 6, 7, 3]
print(boundaryTraversal(TreeNode(1)))  # [1]

Based on this video: Boundary Traversal of Binary Tree