Binary Tree Right Side View: One Node Per Level
Understand LeetCode 199 once, then solve it twice with right-first DFS and level-order BFS.

Picture yourself standing to the right of a binary tree, looking at it edge-on. Some nodes stand in plain sight. Others are hiding behind their neighbours.
Binary Tree Right Side View (LeetCode 199) asks one question:
Which nodes can you see from the right?
It sounds like a geometry puzzle. It's actually a lesson in choosing the right mental model, and once you have it, the code writes itself. Let's build it from the ground up.
The Tree We'll Use Throughout
1
/ \
2 3
/ \ \
4 5 7
/
6
Take a moment and answer this yourself before reading on: what do you see from the right?
If you said [1, 3, 7], you just walked into the trap this problem was designed around.
The correct answer is:
[1, 3, 7, 6]
Node 6 sits in the left subtree, and it is still visible. Nothing is to its right at that depth, so nothing blocks it.
The Trap: "Just Follow the Right Children"
The first instinct is almost always this: start at the root and keep walking node->right until you fall off the tree.
That gives you 1 → 3 → 7, and it quietly drops 6.
Visibility isn't about which child pointer you followed. It's about what else exists at the same depth. A node is hidden only if another node at its level sits further to the right.
The Reframe: One Node Per Level
Slice the tree into horizontal levels:
Level 0 → 1
Level 1 → 2 3
Level 2 → 4 5 7
Level 3 → 6
From the right, you see exactly one node per level: the rightmost one.
Level 0 → 1
Level 1 → 3
Level 2 → 7
Level 3 → 6
Right Side View = the rightmost node of every level.
That one sentence is the entire problem. Everything else is just two different ways of finding that node.
Two Ways to Find the Rightmost Node of Each Level
| Approach | How it sees the tree | What it picks |
|---|---|---|
| DFS (right-first) | Dives deep, right side first | The first node it reaches at each level |
| BFS (level-order) | Sweeps one level at a time | The last node of each level |
Same destination, two different routes. Let's walk both.
Approach 1: DFS, Right First
A standard DFS visits Root → Left → Right. We flip it:
Root → Right → Left
Why? If we always explore the right subtree before the left, then the first time we reach any given depth, we've arrived via the rightmost available path. That first arrival is the visible node, so we record it and ignore everything else that shows up at that depth later.
The One Line That Does the Work
if (res.size() == level)
res.push_back(root->val);
This looks cryptic until you see the invariant behind it:
res.size()is always the number of levels we've already captured.
So when we arrive at a node on level:
res.size() == level: we've captured levels0 … level-1but nothing for this level yet. We're the first to get here, so we're visible. Record it.res.size() > level: this level already has a representative from further right. We're hidden. Skip.
No hash map, no per-level bookkeeping. The size of the result vector is the state.
The Solution
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right)
* : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
dfs(root, 0, res);
return res;
}
private:
void dfs(TreeNode* node, int level, vector<int>& res) {
if (node == nullptr) return;
// First node reached at this depth => the rightmost one
if (static_cast<int>(res.size()) == level) {
res.push_back(node->val);
}
// Right before left is what makes "first" mean "rightmost"
dfs(node->right, level + 1, res);
dfs(node->left, level + 1, res);
}
};
The same idea in TypeScript:
function rightSideView(root: TreeNode | null): number[] {
const res: number[] = [];
const dfs = (node: TreeNode | null, level: number): void => {
if (!node) return;
if (res.length === level) res.push(node.val);
dfs(node.right, level + 1);
dfs(node.left, level + 1);
};
dfs(root, 0);
return res;
}
Dry Run
On our tree, right-first DFS visits nodes in this order:
1 → 3 → 7 → 2 → 5 → 6 → 4
| Visit | Node | level |
res.size() |
Action | res |
|---|---|---|---|---|---|
| 1 | 1 | 0 | 0 | record | [1] |
| 2 | 3 | 1 | 1 | record | [1, 3] |
| 3 | 7 | 2 | 2 | record | [1, 3, 7] |
| 4 | 2 | 1 | 3 | skip, level 1 already has 3 |
[1, 3, 7] |
| 5 | 5 | 2 | 3 | skip, level 2 already has 7 |
[1, 3, 7] |
| 6 | 6 | 3 | 3 | record, first node at level 3 | [1, 3, 7, 6] |
| 7 | 4 | 2 | 4 | skip | [1, 3, 7, 6] |
Look at step 6. The traversal is deep in the left subtree, and node 6 still gets recorded because it's the first node at a depth nobody has reached yet. That's the exact case the "follow right children" shortcut misses.
Approach 2: BFS, Level by Level
If DFS thinks in paths, BFS thinks in layers, which matches how we framed the problem in the first place.
The plan:
Put the root in a queue.
Snapshot the queue size. That's exactly the number of nodes in the current level.
Process that many nodes, pushing their children as you go.
The last node processed in the batch is the rightmost. Record it.
Level 0 → [1] → last: 1
Level 1 → [2, 3] → last: 3
Level 2 → [4, 5, 7] → last: 7
Level 3 → [6] → last: 6
The Solution
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
if (root == nullptr) return res;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size(); // freeze the level boundary
for (int i = 0; i < levelSize; i++) {
TreeNode* node = q.front();
q.pop();
// Last node of this level => visible from the right
if (i == levelSize - 1) {
res.push_back(node->val);
}
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return res;
}
};
The line that matters is int levelSize = q.size();. The queue keeps growing as we push children, so we must capture the size before the inner loop. That snapshot is what turns a flat queue into clean level boundaries.
DFS vs BFS: Which One Should You Reach For?
Both are O(n) in time. The difference is in what they cost and how they fail.
| DFS (Right → Left) | BFS (Level-order) | |
|---|---|---|
| Mental model | First arrival at each depth wins | Last node in each layer wins |
| Takes | First node per level | Last node per level |
| Time | O(n) |
O(n) |
| Extra space | O(h), the recursion stack |
O(w), the queue |
| Worst case for space | Skewed tree, h = n |
Wide, complete tree, w ≈ n/2 |
| Failure mode | Stack overflow on very deep trees | Memory pressure on very wide trees |
Here h is the height of the tree and w is its maximum width.
Rule of thumb:
Reach for DFS when the tree is reasonably balanced and you want the shortest code.
Reach for BFS when the input could be deeply skewed. A linked-list-shaped tree with 100,000 nodes will blow a default recursion stack, while a queue won't care.
In an interview, state the trade-off out loud. Naming the failure mode of each approach is what separates "I memorised a solution" from "I understand the structure."
Edge Cases Worth Checking
| Input | Expected | Why it's interesting |
|---|---|---|
nullptr |
[] |
BFS needs the explicit guard. DFS handles it through its base case. |
| Single node | [1] |
Trivial, but confirms the base path. |
Left-skewed (1 → 2 → 3 via left) |
[1, 2, 3] |
Every node is visible. This breaks the "follow right pointers" shortcut immediately. |
| Right-skewed | Same as input | Right-first DFS never needs to visit the left side. |
A small C++ note: res.size() returns size_t (unsigned), while level is an int. Comparing them directly compiles but triggers sign-comparison warnings, which many teams treat as errors. The static_cast<int> in the DFS solution above keeps it clean.
The Pattern Behind the Pattern
This problem is really a template for a whole family:
Left Side View: mirror it. Visit left first in DFS, or take
i == 0in BFS.Binary Tree Level Order Traversal: the same BFS skeleton, collecting every node instead of one.
Zigzag Level Order: the same skeleton with alternating direction.
Average of Levels / Largest Value in Each Row: aggregate per level instead of picking one node.
Top View / Bottom View: the same "one representative per slot" idea, but slots are horizontal columns instead of depths.
The skill being tested is how you partition a tree: by level, by column, or by path. Once you can name the partition, the traversal is mechanical.
Final Takeaway
Don't ask "which child do I follow?" Ask "what does each level contribute?"
DFS → visit Right first → keep the FIRST node seen at each level
BFS → sweep level by level → keep the LAST node seen at each level
Two traversals, one idea:
Right Side View = the rightmost node of every level.
If this helped, try the mirror image (Left Side View) without looking at the code. If you can write it in under two minutes, the pattern has stuck.
Happy coding. 🌳





