Root-to-Leaf Target Path Sum: find if any path sums to a target

AS
Ackshaey Singh
Difficulty Medium
Time Complexity
O(n)
Space Complexity
O(h)
Depth first search Tree Breadth first search Binary tree
Google Bloomberg Amazon Adobe Uber

You're twenty minutes into your Google phone screen when the interviewer says, "I'm going to give you a binary tree and a number. Tell me if there's a path from the root to any leaf that adds up to that number." You nod, grab your virtual whiteboard, and start sketching a tree. This is Path Sum, one of the most common tree problems in technical interviews, and it tests your ability to combine recursive thinking with careful edge case handling.

TL;DR

Use depth-first search to traverse the tree, subtracting each node's value from the target sum as you go. When you reach a leaf node, check if its value equals the remaining target. If so, a valid path exists. This runs in O(n) time since every node may need to be visited, and uses O(h) space for the call stack where h is the tree height.

Why This Problem Matters

Path Sum sits at the intersection of two concepts interviewers love: binary tree traversal and recursive problem decomposition. It shows up frequently at Google, Bloomberg, Amazon, and Adobe, often as the opening question in a multi-part series. Solve this cleanly, and the interviewer will likely follow up with "now return all valid paths" (Path Sum II) or "what about paths that don't start at the root?" (Path Sum III). Getting the fundamentals right here sets you up for those harder variations.

Binary Trees: A Quick Refresher

A binary tree is a hierarchical data structure where each node has at most two children. Here is the example tree from our problem:

Loading visualization...

Key terminology:

  • Root: The topmost node (5 in our example)
  • Leaf: A node with no children (7, 2, 13, and 1 are all leaves)
  • Path: A sequence of nodes from root to leaf, following parent-child connections
  • Path sum: The total of all node values along a path

Understanding the Problem

Given a binary tree and an integer targetSum, determine whether any root-to-leaf path exists where the node values add up to targetSum.

For our example tree with targetSum = 22:

Loading visualization...

The path 5 -> 4 -> 11 -> 2 sums to 22, so the answer is true.

All root-to-leaf paths and their sums:

  • 5 -> 4 -> 11 -> 7 = 27
  • 5 -> 4 -> 11 -> 2 = 22 (match)
  • 5 -> 8 -> 13 = 26
  • 5 -> 8 -> 4 -> 1 = 18

Edge Cases to Consider

  1. Empty tree (null root): No paths exist, return false
  2. Single node: The node is both root and leaf; check if its value equals the target
  3. Negative values: Node values can be negative, which is perfectly valid
  4. No valid path: Every path may fall short or exceed the target

Solution Approach

The brute-force way to solve this would be to enumerate every root-to-leaf path, compute each sum, and check for a match. That works, but there is a cleaner technique.

The Subtraction Technique

Instead of accumulating a running sum and comparing it at the leaf, subtract each node's value from the target as you traverse downward. When you reach a leaf, you only need to check whether the leaf's value equals the remaining target.

Loading visualization...

For the path 5 -> 4 -> 11 -> 2 with targetSum = 22:

  • At node 5: remaining = 22 - 5 = 17
  • At node 4: remaining = 17 - 4 = 13
  • At node 11: remaining = 13 - 11 = 2
  • At node 2: this is a leaf, and 2 == 2, so return true

This works because the subtraction distributes the comparison across each level. At the leaf, the remaining value is targetSum - (sum of all ancestors), which equals the leaf's value only if the full path sums to the target.

Recursive Structure

The algorithm fits naturally into a recursive DFS:

  1. Base case (null node): Return false. There is no path through a null node.
  2. Leaf check: If the current node has no children, return whether its value equals the remaining target.
  3. Recursive step: Subtract the current node's value from the target and recurse on both children. Return true if either subtree contains a valid path.

Tracing Through the Example

For our tree with targetSum = 22, here is how the recursion finds the valid path through the left subtree:

Loading visualization...

The recursion reaches leaf node 2 with a remaining target of 2. Since 2 == 2, it returns true, and that result propagates back up through the call chain.

Meanwhile, the right subtree path 5 -> 8 -> 13 fails:

Loading visualization...

Node 13 is a leaf, but 13 != 9, so this branch returns false. The algorithm continues checking other branches (the 4 -> 1 subtree), which also fails. Because the left subtree already found a valid path, the overall result is true.

Negative Values

The algorithm handles negative values without any special logic:

Loading visualization...

For targetSum = -5: remaining at node -2 is -5 - (-2) = -3. Node -3 is a leaf, and -3 == -3, so the answer is true.

Implementation

Prefer a different language? Jump to solutions in other languages.

public class Solution {

  public boolean hasPathSum(TreeNode root, int targetSum) {
    // Base case: null node means no path exists here
    if (root == null) {
      return false;
    }

    // Leaf node: check if this node's value matches the remaining target
    if (root.left == null && root.right == null) {
      return targetSum == root.data;
    }

    // Subtract current node's value and recurse on both children
    int newTargetSum = targetSum - root.data;
    return hasPathSum(root.left, newTargetSum) || hasPathSum(root.right, newTargetSum);
  }
}

Three things to note about this implementation:

  1. The null check comes first. This handles both empty trees and the case where a node has only one child (the missing child is null).
  2. The leaf check uses both root.left == null and root.right == null. A node with one child is not a leaf. Forgetting this check is the most common bug in Path Sum implementations.
  3. Short-circuit evaluation. The || operator means if the left subtree returns true, the right subtree is never explored.

Complexity Analysis

Time Complexity: O(n)

Every node is visited at most once. In the worst case (no valid path exists), the algorithm explores the entire tree. Each visit performs constant work: one subtraction, one or two comparisons, and up to two recursive calls.

Space Complexity: O(h)

The space is determined by the maximum depth of the recursion stack, which equals the height h of the tree.

  • Balanced tree: h = O(log n), so space is O(log n)
  • Skewed tree (all nodes on one side): h = n, so space is O(n)

There is no auxiliary data structure, just the call stack.

Common Pitfalls

  1. Checking sum at internal nodes instead of leaves. The problem requires root-to-leaf paths. If you return true when targetSum - root.data == 0 at a non-leaf node, you will produce false positives for trees where an internal node's running sum happens to match.

  2. Treating a node with one child as a leaf. A node with a left child but no right child is not a leaf. The path must continue through its existing child.

  3. Forgetting the empty tree case. If root is null, the answer is false, not true (even when targetSum is 0). An empty tree has no paths.

  4. Ignoring negative values. Some candidates assume all values are positive and try to prune branches where the running sum exceeds the target. This optimization is invalid when nodes can have negative values.

Interview Tips

When you see this problem in an interview:

  1. Clarify the definition of "leaf." Confirm that a leaf is a node with zero children. This prevents the most common bug.

  2. Mention the subtraction technique up front. Saying "I'll subtract each node's value from the target as I go, so at the leaf I just compare" shows you have a plan before writing code.

  3. Walk through one valid and one invalid path. Tracing 5 -> 4 -> 11 -> 2 (valid) and 5 -> 8 -> 13 (invalid) demonstrates correctness and catches logic errors.

  4. Discuss the iterative alternative. Mentioning that you could use an explicit stack with (node, remaining) pairs shows breadth of knowledge. The recursive approach is cleaner for interviews, but knowing both is a plus.

  5. Anticipate follow-ups. Be ready for "return all valid paths" (Path Sum II, uses backtracking) and "paths can start anywhere" (Path Sum III, uses prefix sums).

Key Takeaways

  • Subtract the current node's value from the target at each step so the leaf check reduces to a single equality comparison.
  • A leaf is a node where both left and right are null. Checking only one side causes false positives.
  • The recursion short-circuits via ||. If the left subtree finds a valid path, the right subtree is skipped entirely.
  • Time complexity is O(n) and space complexity is O(h), making this optimal for the problem constraints.
  • This recursive DFS pattern (null check, leaf check, recurse on children) is the foundation for Path Sum II, Path Sum III, and many other tree traversal problems.

Solutions in Other Languages

Python

class Solution:
    def has_path_sum(self, root, target_sum):
        if not root:
            return False

        if not root.left and not root.right:
            return root.data == target_sum

        target_sum -= root.data
        return (self.has_path_sum(root.left, target_sum) or
                self.has_path_sum(root.right, target_sum))

JavaScript

class Solution {
  hasPathSum(root, targetSum) {
    if (!root) {
      return false;
    }

    if (!root.left && !root.right) {
      return targetSum === root.data;
    }

    const newTargetSum = targetSum - root.data;
    return this.hasPathSum(root.left, newTargetSum) || this.hasPathSum(root.right, newTargetSum);
  }
}

TypeScript

class Solution {
  hasPathSum(root: TreeNode | null, targetSum: number): boolean {
    if (root === null) return false;

    if (root.left === null && root.right === null) {
      return root.data === targetSum;
    }

    const newTargetSum = targetSum - root.data;
    return this.hasPathSum(root.left, newTargetSum) || this.hasPathSum(root.right, newTargetSum);
  }
}

C++

class Solution {
public:
  bool hasPathSum(TreeNode *root, int targetSum) {
    if (!root) {
      return false;
    }

    if (!root->left && !root->right) {
      return targetSum == root->data;
    }

    int newTargetSum = targetSum - root->data;
    return hasPathSum(root->left, newTargetSum) || hasPathSum(root->right, newTargetSum);
  }
};

Go

func (s *Solution) HasPathSum(root *TreeNode, targetSum int) bool {
    if root == nil {
        return false
    }

    if root.Left == nil && root.Right == nil {
        return root.Data == targetSum
    }

    remainingSum := targetSum - root.Data
    return s.HasPathSum(root.Left, remainingSum) || s.HasPathSum(root.Right, remainingSum)
}

Kotlin

fun hasPathSum(root: TreeNode?, targetSum: Int): Boolean {
    if (root == null) {
        return false
    }

    if (root.left == null && root.right == null) {
        return targetSum == root.data
    }

    val newTargetSum = targetSum - root.data
    return hasPathSum(root.left, newTargetSum) || hasPathSum(root.right, newTargetSum)
}

Scala

def hasPathSum(root: TreeNode, targetSum: Int): Boolean = {
    if (root == null) return false

    if (root.left == null && root.right == null) {
        return root.data == targetSum
    }

    val newTargetSum = targetSum - root.data
    hasPathSum(root.left, newTargetSum) || hasPathSum(root.right, newTargetSum)
}

Swift

func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool {
    if root == nil {
        return false
    }

    if root!.left == nil && root!.right == nil {
        return targetSum == root!.data
    }

    let newTargetSum = targetSum - root!.data
    return hasPathSum(root!.left, newTargetSum) || hasPathSum(root!.right, newTargetSum)
}

Ruby

def has_path_sum?(root, target_sum)
    return false if root.nil?

    if root.left.nil? && root.right.nil?
        return target_sum == root.data
    end

    new_target_sum = target_sum - root.data
    has_path_sum?(root.left, new_target_sum) || has_path_sum?(root.right, new_target_sum)
end

Rust

The Rust version uses a helper function to work with references, avoiding ownership issues with Option<Box<TreeNode>>.

impl Solution {
    pub fn has_path_sum(&self, root: Option<Box<TreeNode>>, target_sum: i32) -> bool {
        Self::helper(&root, target_sum)
    }

    fn helper(node: &Option<Box<TreeNode>>, target_sum: i32) -> bool {
        if node.is_none() {
            return false;
        }

        let node = node.as_ref().unwrap();

        if node.left.is_none() && node.right.is_none() {
            return target_sum == node.data;
        }

        let new_target_sum = target_sum - node.data;
        Self::helper(&node.left, new_target_sum) || Self::helper(&node.right, new_target_sum)
    }
}

C#

public bool HasPathSum(TreeNode? root, int targetSum) {
    if (root == null) {
        return false;
    }

    if (root.left == null && root.right == null) {
        return targetSum == root.data;
    }

    int newTargetSum = targetSum - root.data;
    return HasPathSum(root.left, newTargetSum) || HasPathSum(root.right, newTargetSum);
}

Dart

bool hasPathSum(TreeNode? root, int targetSum) {
    if (root == null) {
        return false;
    }

    if (root.left == null && root.right == null) {
        return targetSum == root.data;
    }

    int newTargetSum = targetSum - root.data;
    return hasPathSum(root.left, newTargetSum) || hasPathSum(root.right, newTargetSum);
}

PHP

public function hasPathSum(?TreeNode $root, int $targetSum): bool {
    if ($root === null) {
        return false;
    }

    if ($root->left === null && $root->right === null) {
        return $targetSum === $root->data;
    }

    $newTargetSum = $targetSum - $root->data;
    return $this->hasPathSum($root->left, $newTargetSum) || $this->hasPathSum($root->right, $newTargetSum);
}

Related Problems

Once you are comfortable with Path Sum, try these to build on the same recursive DFS foundation:

  • Path Sum II - return all valid root-to-leaf paths (adds backtracking)
  • Path Sum III - paths can start at any node (uses prefix sums)
  • Binary Tree Maximum Path Sum - maximum sum path across any two nodes (harder variation)
  • Height of a Binary Tree - same recursive structure, simpler aggregation

This problem and hundreds of others are available on Firecode, where over 50,000 engineers have prepared for technical interviews and landed roles at top companies. Consistent practice with problems like Path Sum builds the recursive thinking skills that interviews demand.

Frequently Asked Questions

What is the time complexity of the Path Sum solution?
O(n) where n is the number of nodes in the tree. In the worst case, every node must be visited to determine if any root-to-leaf path matches the target sum. Each node is processed exactly once during the DFS traversal.
What is the space complexity?
O(h) where h is the height of the tree, due to the recursive call stack. For a balanced binary tree this is O(log n). For a completely skewed tree (every node has only one child), the height equals n, so the worst case is O(n).
Why do we subtract the current node's value instead of accumulating a running sum?
Both approaches work, but subtracting from targetSum is cleaner because the leaf check becomes a simple comparison: does the leaf's value equal the remaining target? With a running sum, you would need to pass an additional accumulator parameter and compare it to the original targetSum at the leaf.
How does the algorithm handle negative node values?
The algorithm handles negative values naturally. Since it subtracts each node's value from the remaining target, negative values effectively increase the remaining target. For example, a node with value -3 and remaining target 5 produces a new remaining target of 5 - (-3) = 8.
What is the difference between Path Sum and Path Sum II?
Path Sum (this problem) returns a boolean indicating whether any valid path exists. Path Sum II asks you to return all root-to-leaf paths that sum to the target. Path Sum II requires backtracking to collect each valid path into a list, while Path Sum can short-circuit and return true as soon as one valid path is found.
Can this problem be solved iteratively?
Yes. Use a stack (or queue for BFS) that stores pairs of (node, remaining sum). Pop a pair, check if the node is a leaf with remaining sum equal to its value, and if not, push its children with updated remaining sums. The iterative approach avoids stack overflow for extremely deep trees but uses O(n) explicit memory.
Why must the path go from root to leaf?
The problem specifically requires root-to-leaf paths. A leaf is defined as a node with no children. This constraint matters because a subtree sum might match the target at an internal node, but that does not count. Always verify both root.left and root.right are null before checking the sum.
What happens when the tree is empty?
When root is null, the function returns false. An empty tree has no paths at all, so no path can sum to the target. This serves as the base case for the recursion and also handles the edge case of calling the function on a null tree directly.
How does Path Sum relate to other tree DFS problems?
Path Sum uses the same recursive DFS skeleton as finding tree height, checking tree symmetry, or validating BSTs. The pattern is: handle the null base case, process the current node, then recurse on children. Mastering this pattern transfers directly to dozens of tree problems.
How often does Path Sum appear in coding interviews?
Path Sum is a popular warm-up tree question at companies like Google, Bloomberg, Amazon, and Adobe. It commonly appears as the first question in a series, followed by Path Sum II or Path Sum III. It tests recursive thinking and binary tree traversal, both fundamental skills for technical interviews in 2026.