DSATrees

Trees

A tree is a hierarchical data structure made up of nodes connected by edges. Unlike arrays or linked lists — which are linear — a tree branches out from a single starting point, making it the natural choice for representing parent-child relationships, file systems, organisation charts, and the structure of code itself (the AST your compiler builds is a tree).

Why trees?

Linear structures force you to scan from one end to the other. Trees let you divide the search space with every step. A balanced tree of 1 000 000 nodes can be searched in about 20 comparisons — the same reason binary search is so powerful, but generalised to arbitrary hierarchies.

  • Hierarchical data — file systems, DOM, org charts, categories

  • Fast search / insert / delete — O(log n) on balanced trees

  • Ordering — BSTs keep data sorted for free

  • Priority queues — heaps are specialised trees

  • Prefix lookups — tries power autocomplete and spell-check

  • Compiler internals — ASTs represent parsed source code

Terminology

Anatomy of a tree

Text
                  A          ← root (no parent)
                /   \
               B     C        ← A is parent; B and C are children of A
              / \     \
             D   E     F      ← D, E, F are leaf nodes (no children)

  Node A: depth 0, level 1
  Node B: depth 1, level 2
  Node D: depth 2, level 3  ← leaf

  Height of tree = 2  (longest path root → leaf, counted in edges)
  Height of node B = 1 (longest path from B to a leaf below it)

Term

Definition

Root

The single node with no parent; the entry point of the tree.

Node

Any element in the tree.

Edge

A link connecting a parent to a child.

Parent

A node that has one or more children.

Child

A node directly connected below a parent.

Siblings

Nodes that share the same parent.

Leaf

A node with no children (also called an external node).

Internal node

Any non-leaf node.

Depth of a node

Number of edges from the root to that node.

Height of a node

Number of edges on the longest path from that node down to a leaf.

Height of a tree

Height of the root node.

Level

depth + 1 (root is at level 1).

Subtree

A node together with all of its descendants.

Degree

Number of children a node has.

Path

A sequence of nodes connected by edges.

Ancestor

Any node on the path from a node up to the root.

Descendant

Any node reachable going downward from a given node.

Trees as a recursive structure

The most important insight about trees: a tree is a root node whose children are themselves trees (subtrees). This recursive definition drives almost every algorithm — traversal, search, height calculation, and insertion all reduce to "do something at this node, then recurse into each child."

Generic tree node

JS
class TreeNode {
  constructor(value) {
    this.value = value
    this.children = []   // N-ary: any number of children
  }
}

// Recursive height — height of a tree rooted at node
function height(node) {
  if (!node) return -1                     // empty tree: -1
  if (node.children.length === 0) return 0 // leaf: height 0
  return 1 + Math.max(...node.children.map(height))
}

// Recursive size — total number of nodes
function size(node) {
  if (!node) return 0
  return 1 + node.children.reduce((sum, child) => sum + size(child), 0)
}
Note
Height can be defined as edge count (above) or node count (height + 1). Interview problems usually mean edge count; clarify when in doubt.
Types of trees

Binary Tree

Every node has at most 2 children, conventionally called left and right. The most common tree in interviews — see the Binary Trees page for a deep dive.

Text
       1
      / \
     2   3
    / \
   4   5

Binary Search Tree (BST)

A binary tree with the BST property: for every node N, all values in N's left subtree are less than N's value, and all values in the right subtree are greater. This makes search, insert, and delete O(h) where h is the height — O(log n) when balanced.

BST property

Text
       8
      / \
     3   10
    / \    \
   1   6    14
      / \   /
     4   7 13

  left(3) < 8 < right(10)  ✓
  In-order traversal → 1 3 4 6 7 8 10 13 14  (sorted!)

BST insert and search

JS
class BSTNode {
  constructor(val) { this.val = val; this.left = null; this.right = null }
}

function insert(root, val) {
  if (!root) return new BSTNode(val)
  if (val < root.val) root.left  = insert(root.left,  val)
  else                root.right = insert(root.right, val)
  return root
}

function search(root, val) {
  if (!root || root.val === val) return root
  return val < root.val ? search(root.left, val) : search(root.right, val)
}

// Build BST from array
function buildBST(values) {
  return values.reduce((root, v) => insert(root, v), null)
}

const bst = buildBST([8, 3, 10, 1, 6, 14, 4, 7, 13])
console.log(search(bst, 6)?.val)  // 6
console.log(search(bst, 99))      // null

AVL Tree (Self-Balancing BST)

An AVL tree maintains the balance factor of every node: the difference in height between left and right subtrees must be at most 1. After each insert or delete, the tree performs rotations to restore balance, guaranteeing O(log n) for all operations.

Balance factor = height(left) - height(right)

Text
  Unbalanced (BF = -2):      After left rotation:
        3                              5
         \                            / \
          5           →              3   7
           \
            7

  Balance factor must be in {-1, 0, 1} for every node.

Heap

A complete binary tree satisfying the heap property. In a max-heap, every parent is greater than or equal to its children; in a min-heap, every parent is smaller. Heaps are stored efficiently in arrays and power priority queues. The root is always the max (or min) element — O(1) access.

Max-heap

Text
          90
         /  \
        75   85
       /  \  / \
      55  60 70  80

  Array representation: [90, 75, 85, 55, 60, 70, 80]
  Parent of index i  → Math.floor((i - 1) / 2)
  Left child of i   → 2*i + 1
  Right child of i  → 2*i + 2

Trie (Prefix Tree)

A trie stores strings character by character. Each node represents one character; paths from root to a marked node spell out words. Lookup and insert are O(L) where L is the string length — independent of how many words are stored.

Trie storing: "cat", "car", "card", "care", "bat"

Text
        root
       /    \
      c      b
      |      |
      a      a
     / \     |
    t   r    t*
   *   / \
      d*  e*

  * = end of word
  "car" shares "ca" with "cat" — prefix compression saves space.

Trie implementation

JS
class TrieNode {
  constructor() {
    this.children = {}   // char → TrieNode
    this.isEnd = false
  }
}

class Trie {
  constructor() { this.root = new TrieNode() }

  insert(word) {
    let node = this.root
    for (const ch of word) {
      if (!node.children[ch]) node.children[ch] = new TrieNode()
      node = node.children[ch]
    }
    node.isEnd = true
  }

  search(word) {
    let node = this.root
    for (const ch of word) {
      if (!node.children[ch]) return false
      node = node.children[ch]
    }
    return node.isEnd
  }

  startsWith(prefix) {
    let node = this.root
    for (const ch of prefix) {
      if (!node.children[ch]) return false
      node = node.children[ch]
    }
    return true   // prefix exists (word may or may not be complete)
  }
}

const trie = new Trie()
;['cat', 'car', 'card', 'care', 'bat'].forEach(w => trie.insert(w))
console.log(trie.search('care'))      // true
console.log(trie.search('ca'))        // false (not a complete word)
console.log(trie.startsWith('ca'))    // true

N-ary Tree

A generalisation of binary trees where each node may have any number of children. File systems (a directory can have many subdirectories), organisation hierarchies, and HTML/XML DOMs are all N-ary trees.

N-ary tree (file system)

Text
  /
  ├── home/
  │   ├── user/
  │   │   ├── docs/
  │   │   └── pics/
  │   └── guest/
  └── etc/
      └── nginx/
Tree traversal overview

Traversal is visiting every node exactly once. The order in which you visit determines what you see. Binary trees support four canonical traversals; general trees add level-order (BFS).

Traversal

Order

Common uses

Pre-order (DFS)

Root → Left → Right

Copy a tree, serialise, prefix expressions

In-order (DFS)

Left → Root → Right

Get sorted values from a BST

Post-order (DFS)

Left → Right → Root

Delete a tree, evaluate expressions, size

Level-order (BFS)

Level by level, left to right

Shortest path, level-wise processing

All four traversals

JS
function preOrder(node, result = []) {
  if (!node) return result
  result.push(node.val)          // Root
  preOrder(node.left,  result)   // Left
  preOrder(node.right, result)   // Right
  return result
}

function inOrder(node, result = []) {
  if (!node) return result
  inOrder(node.left,  result)    // Left
  result.push(node.val)          // Root
  inOrder(node.right, result)    // Right
  return result
}

function postOrder(node, result = []) {
  if (!node) return result
  postOrder(node.left,  result)  // Left
  postOrder(node.right, result)  // Right
  result.push(node.val)          // Root
  return result
}

function levelOrder(root) {
  if (!root) return []
  const result = [], queue = [root]
  while (queue.length) {
    const level = []
    const len = queue.length      // snapshot — important!
    for (let i = 0; i < len; i++) {
      const node = queue.shift()
      level.push(node.val)
      if (node.left)  queue.push(node.left)
      if (node.right) queue.push(node.right)
    }
    result.push(level)
  }
  return result
}
Time and space complexity summary

Tree type

Search

Insert

Delete

Space

Binary Search Tree (balanced)

O(log n)

O(log n)

O(log n)

O(n)

Binary Search Tree (worst)

O(n)

O(n)

O(n)

O(n)

AVL Tree

O(log n)

O(log n)

O(log n)

O(n)

Min/Max Heap

O(n)

O(log n)

O(log n)

O(n)

Trie

O(L)

O(L)

O(L)

O(ALPHABET × L × n)

N-ary Tree

O(n)

O(1)

O(n)

O(n)

Note
L = length of the key string for trie operations. All traversals are O(n) time and O(h) space on the call stack (O(n) worst case for a skewed tree, O(log n) for a balanced tree).
Choosing the right tree
  1. Need sorted order + fast search/insert? Use a BST or AVL tree.

  2. Need the minimum or maximum instantly? Use a heap (priority queue).

  3. Need prefix-based string search / autocomplete? Use a trie.

  4. Modelling a hierarchy with arbitrary branching? Use an N-ary tree.

  5. Need to traverse a graph layer by layer? BFS on a tree is level-order.

Common interview patterns
  • DFS (recursion or explicit stack) — height, diameter, path sum, lowest common ancestor

  • BFS (queue) — level order, minimum depth, right-side view

  • In-order of BST — validate BST, kth smallest, recover BST

  • Post-order — bottom-up answers (diameter, balanced check, subtree sums)

  • Serialise / deserialise — convert tree to string and back (pre-order + sentinel)

Tip
When you are stuck on a tree problem, ask: does the answer for a node depend on answers from its children (post-order), or do the children need information from the parent (pre-order)? That question determines the recursion direction and what to return vs. what to pass down.
Warning
A linked list is technically a degenerate tree (each node has one child). An unbalanced BST built by inserting already-sorted data degenerates into a linked list — O(n) for everything. Always prefer self-balancing trees (AVL, Red-Black) or explicitly balance the input before building a BST.