Algorithm

Balanced Binary Tree

Trees Pattern

Balanced Binary Tree

Given the root of a binary tree, return true if the tree is height-balanced. Height-balanced means that at every node in the tree, the heights of its left and right subtrees differ by at most 1 — not just at the root. An empty tree is balanced, so a null root returns true. You return a boolean only, not the height.

CONSTRAINTS
  • The number of nodes in the tree is in the range [0, 5000]
  • -10⁴ <= Node.val <= 10⁴
  • The balance condition must hold at every node, not only at the root
  • An empty subtree has height 0
EXAMPLE 1
Input: root = [3,9,20,null,null,15,7]
Output: true
At the root, the left side (just 9) has height 1 and the right side (20 with its two children) has height 2 — a difference of 1, which is allowed. Every deeper node has two empty or equal sides, so the condition holds everywhere.
EXAMPLE 2
Input: root = [1,2,2,3,3,null,null,4,4]
Output: false
The root's left subtree reaches down three levels while its right subtree is a single node, a difference of 2. One violation anywhere is enough to make the whole answer false.
EXAMPLE 3
Input: root = [1,2,3,4,5,6,null,8]
Output: true
The tree is visibly lopsided — the left side is heavier — yet no single node's two sides differ by more than 1. Balance is a strictly local condition checked everywhere, not a judgement about how symmetric the tree looks.
EXAMPLE 4
Input: root = []
Output: true
There is no node at which the condition could fail, so an empty tree is balanced by definition rather than as a special case.
Does the condition apply only at the root, or at every node?
Every node. A tree whose root looks even can still be unbalanced deep inside, and that is the case most naive attempts get wrong.
How is height defined — nodes or edges?
Nodes here, matching Maximum Depth: an empty subtree is 0 and a leaf is 1. The convention does not change the answer, since only the *difference* of two heights matters, but mixing conventions within one solution does.
Should I return the height as well as the verdict?
The required return is just the boolean. Whether you compute heights internally is your business — and it turns out to matter a great deal for efficiency.
Is 'balanced' here the same as a perfectly balanced or complete tree?
No, it is looser. A difference of 1 is permitted at every node, so plenty of visibly uneven trees qualify. It is the AVL condition, which is enough to keep the height O(log N).

The definition is doing more work than it first appears. "The two subtrees differ in height by at most 1" is not a statement about the root — it is a statement that has to hold at every single node. A tree can look perfectly even at the top and still be disqualified by some lopsided node buried five levels down, so the answer is a giant AND across all nodes: the tree is balanced only if nobody fails.

The direct translation, and why it is slow

Write the definition out literally and you get a working solution immediately. We already know how to measure a subtree's height from Maximum Depth, so: a node is fine if the two heights it sees differ by at most 1, and its left subtree is fine, and its right subtree is fine.

python
def height(node):
    if node is None: return 0
    return 1 + max(height(node.left), height(node.right))

def isBalanced(node):
    if node is None: return True
    if abs(height(node.left) - height(node.right)) > 1: return False
    return isBalanced(node.left) and isBalanced(node.right)

Correct, and wasteful in a way worth measuring rather than waving at. Every call to height walks an entire subtree, so checking the root costs a full O(N) sweep. Then we recurse and check the root's left child — which sweeps that subtree all over again, even though the sweep we just finished had already computed its height and thrown the number away. Every node ends up being touched once for each of its ancestors, so the total work is the sum of all subtree sizes, which is roughly N × H. On a balanced tree H is about log₂N and this is tolerable; on a chain-shaped tree H is N and it degrades to O(N²) — around 25 million node-visits at the 5000-node limit, for a question that should need 5000.

Name the waste precisely, because the fix is its exact opposite: we compute each height twice over — once inside a parent's measurement, and again when that subtree's own turn comes — and discard the answer in between.

One walk that carries two facts

The two recursions have identical shapes. height descends the whole tree; isBalanced descends the whole tree. They are the same walk, run twice, one nested inside the other. So merge them: perform a single post-order walk in which each node returns its height to its parent and has already verified balance for everything beneath it.

That works because of the order in which facts become available. When a node receives both children's heights, it holds — at that exact moment, for free — everything needed to test its own condition. There is no reason to schedule a second visit later; the check belongs right there, in the middle of the height computation.

The remaining problem is the return channel. The parent needs a height, but it also needs to hear about a failure discovered deep below. Rather than return a pair, use a value that cannot be a genuine height as a failure signal: real heights are 0 or more, so -1 is free to mean "something under here is broken." A node returns its true height if all is well, or -1 if it or anything below it violated the rule.

python
def check(node):
    if node is None:
        return 0                      # empty subtree: height 0, trivially fine

    left = check(node.left)
    if left == -1: return -1          # failure below — stop measuring, just report it

    right = check(node.right)
    if right == -1: return -1

    if abs(left - right) > 1:
        return -1                     # this node is the violator

    return 1 + max(left, right)       # fine: report the real height upward

return check(root) != -1
Crucial Note-1 works as a failure flag only because it is impossible as a real answer — heights start at 0 and go up. Pick a sentinel that a valid computation could produce and the code silently misreads correct data as an error. If a problem's valid range leaves no spare value (heights could legitimately be any integer, say), return a pair or an object instead. That reasoning matters more than this particular -1, and it recurs whenever a recursion has to carry both a measurement and a verdict.

Also note the early returns. The moment a child reports -1, the answer is already decided, so we skip the sibling entirely — this is genuine short-circuiting, not just tidiness, and on a badly broken tree it can end the whole traversal near the first violation.

Worked example:root 1, both children 2; the left 2 has children 3 and 3; the left 3 has children 4 and 4
- Each of the two 4s is a leaf: children return 0, difference 0, so each reports height 1.
- The left 3 sees (1, 1): difference 0, reports 2. The right 3 is a leaf, reports 1.
- The left 2 sees (2, 1): difference 1, which is allowed, so it reports 3. The right 2 is a leaf, reports 1.
- The root sees (3, 1). The difference is 2, which exceeds 1, so it returns -1 and the answer is false.

Follow what happened at the left 2 carefully: it passed its own test and reported honestly. Balance is not violated by any single node being deep — only by a node whose two sides disagree by too much. The root was the first place that comparison went wrong.

The trade

Every node is now entered exactly once and does constant work — two calls, two comparisons, one max — so time drops from O(N × H) to O(N). Extra space stays at O(H) for the recursion stack, unchanged. We paid nothing at all for this speedup; we simply stopped throwing away numbers we had already computed.

That is the reflex worth building: when a solution calls a helper that re-walks the same structure the outer recursion is already walking, fuse them. The test is easy to apply — ask whether the information the helper recomputes was available, in the outer walk, at the moment it was needed. Here it was, exactly at the post-order combine step. Diameter of Binary Tree is the same fusion again with a slightly different twist: the quantity we want (the longest bend through a node) is not the quantity the recursion must return (the height), so instead of a sentinel we keep a running maximum on the side.

Interactive Strategy Visualization

Balance Calculus

1234ABS(1 - 0)DIFF: 1
Checking Node 2. |1-0| <= 1. OK.
Recursive Height Check
Logic Trace
Visiting nodes in Post-Order. Comparing sub-heights...
Rule Definition
abs(height(L) - height(R)) ≤ 1

"A single -1 return bubbles up the failure immediately (Early Exit)."

Strategy

We calculate height and check balance in one pass. If a subtree is unbalanced, we return -1 instead of height.

"Height = max(L, R) + 1"

O(N × H) Height Recomputed Per Node
O(N) Time · O(H) Space Single Fused Pass