dsaintermediate
Trees & Graphs — Traversal, BFS, DFS & Common Patterns
Master binary trees, BSTs, and graph traversal — DFS, BFS, recursion patterns, and 12 classic interview problems with TypeScript solutions.
LearnixoApril 16, 20267 min read
DSATreesGraphsBFSDFSBinary Search TreeInterview Prep
Trees and graphs appear in nearly every senior-level interview. The key insight: almost every tree problem is solved with DFS (recursion), and almost every graph problem is solved with BFS (shortest path) or DFS (connectivity/cycle detection).
Binary Tree Fundamentals
TYPESCRIPT
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}Tree Traversals
TYPESCRIPT
// Inorder — Left → Root → Right (gives sorted order for BST)
function inorder(root: TreeNode | null): number[] {
if (!root) return [];
return [...inorder(root.left), root.val, ...inorder(root.right)];
}
// Preorder — Root → Left → Right (used to copy/serialize trees)
function preorder(root: TreeNode | null): number[] {
if (!root) return [];
return [root.val, ...preorder(root.left), ...preorder(root.right)];
}
// Postorder — Left → Right → Root (used to delete trees, evaluate expressions)
function postorder(root: TreeNode | null): number[] {
if (!root) return [];
return [...postorder(root.left), ...postorder(root.right), root.val];
}
// Iterative inorder (common in interviews)
function inorderIterative(root: TreeNode | null): number[] {
const result: number[] = [];
const stack: TreeNode[] = [];
let curr: TreeNode | null = root;
while (curr || stack.length > 0) {
while (curr) { stack.push(curr); curr = curr.left; }
curr = stack.pop()!;
result.push(curr.val);
curr = curr.right;
}
return result;
}Level Order (BFS)
TYPESCRIPT
function levelOrder(root: TreeNode | null): number[][] {
if (!root) return [];
const result: number[][] = [];
const queue: TreeNode[] = [root];
while (queue.length > 0) {
const levelSize = queue.length;
const level: number[] = [];
for (let i = 0; i < levelSize; 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: O(n) | Space: O(n)Classic Tree Problems
Maximum Depth of Binary Tree
TYPESCRIPT
function maxDepth(root: TreeNode | null): number {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
// Time: O(n) | Space: O(h) where h = heightValidate Binary Search Tree
TYPESCRIPT
function isValidBST(
root: TreeNode | null,
min = -Infinity,
max = Infinity,
): boolean {
if (!root) return true;
if (root.val <= min || root.val >= max) return false;
return isValidBST(root.left, min, root.val)
&& isValidBST(root.right, root.val, max);
}
// Time: O(n) | Space: O(h)Lowest Common Ancestor (LCA)
TYPESCRIPT
function lowestCommonAncestor(
root: TreeNode | null,
p: TreeNode,
q: TreeNode,
): TreeNode | null {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left && right) return root; // p and q on different sides — this is LCA
return left ?? right; // one side found both
}
// Time: O(n) | Space: O(h)Binary Tree Maximum Path Sum
TYPESCRIPT
function maxPathSum(root: TreeNode | null): number {
let globalMax = -Infinity;
function dfs(node: TreeNode | null): number {
if (!node) return 0;
const left = Math.max(0, dfs(node.left)); // ignore negative paths
const right = Math.max(0, dfs(node.right));
// This node as the "turn" point — include both children
globalMax = Math.max(globalMax, node.val + left + right);
// Return the max single path extending from this node
return node.val + Math.max(left, right);
}
dfs(root);
return globalMax;
}
// Time: O(n) | Space: O(h)Serialize and Deserialize Binary Tree
TYPESCRIPT
function serialize(root: TreeNode | null): string {
if (!root) return 'null';
return `${root.val},${serialize(root.left)},${serialize(root.right)}`;
}
function deserialize(data: string): TreeNode | null {
const nodes = data.split(',');
let i = 0;
function build(): TreeNode | null {
if (nodes[i] === 'null') { i++; return null; }
const node = new TreeNode(Number(nodes[i++]));
node.left = build();
node.right = build();
return node;
}
return build();
}Graphs
A graph is a set of nodes (vertices) connected by edges. Can be directed or undirected, weighted or unweighted.
TYPESCRIPT
// Adjacency list representation (most common in interviews)
type Graph = Map<number, number[]>;
const graph: Graph = new Map([
[0, [1, 2]],
[1, [0, 3]],
[2, [0, 4]],
[3, [1]],
[4, [2]],
]);DFS — Depth-First Search
TYPESCRIPT
function dfs(graph: Graph, start: number): number[] {
const visited = new Set<number>();
const result: number[] = [];
function explore(node: number): void {
if (visited.has(node)) return;
visited.add(node);
result.push(node);
for (const neighbor of graph.get(node) ?? []) {
explore(neighbor);
}
}
explore(start);
return result;
}
// Time: O(V + E) | Space: O(V)BFS — Breadth-First Search (Shortest Path)
TYPESCRIPT
function bfs(graph: Graph, start: number): number[] {
const visited = new Set<number>([start]);
const queue = [start];
const result: number[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
result.push(node);
for (const neighbor of graph.get(node) ?? []) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
return result;
}
// Time: O(V + E) | Space: O(V)Number of Islands (Grid DFS)
TYPESCRIPT
function numIslands(grid: string[][]): number {
const rows = grid.length, cols = grid[0].length;
let count = 0;
function sink(r: number, c: number): void {
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] !== '1') return;
grid[r][c] = '0'; // mark visited by "sinking" the land
sink(r + 1, c); sink(r - 1, c);
sink(r, c + 1); sink(r, c - 1);
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++;
sink(r, c);
}
}
}
return count;
}
// Time: O(m × n) | Space: O(m × n) call stackShortest Path (BFS on Grid)
TYPESCRIPT
function shortestPath(grid: number[][], start: [number,number], end: [number,number]): number {
const rows = grid.length, cols = grid[0].length;
const visited = Array.from({ length: rows }, () => new Array(cols).fill(false));
const queue: [[number, number], number][] = [[start, 0]];
const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
visited[start[0]][start[1]] = true;
while (queue.length > 0) {
const [[r, c], dist] = queue.shift()!;
if (r === end[0] && c === end[1]) return dist;
for (const [dr, dc] of dirs) {
const nr = r + dr, nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& !visited[nr][nc] && grid[nr][nc] === 0) {
visited[nr][nc] = true;
queue.push([[nr, nc], dist + 1]);
}
}
}
return -1; // no path found
}
// Time: O(m × n) | Space: O(m × n)Course Schedule — Cycle Detection (Topological Sort)
TYPESCRIPT
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
const graph: number[][] = Array.from({ length: numCourses }, () => []);
for (const [course, prereq] of prerequisites) {
graph[prereq].push(course);
}
// 0 = unvisited, 1 = in-progress (cycle!), 2 = done
const state = new Array(numCourses).fill(0);
function hasCycle(node: number): boolean {
if (state[node] === 1) return true; // back edge — cycle!
if (state[node] === 2) return false; // already fully processed
state[node] = 1;
for (const neighbor of graph[node]) {
if (hasCycle(neighbor)) return true;
}
state[node] = 2;
return false;
}
for (let i = 0; i < numCourses; i++) {
if (hasCycle(i)) return false;
}
return true;
}
// Time: O(V + E) | Space: O(V + E)Quick Reference — Pattern Recognition
Tree problem → try DFS recursion first
"Max/min of subtree" → postorder (compute children first)
"Path from root" → preorder (track running state)
"Validate structure" → pass bounds down as parameters
"Level-by-level" → BFS with queue
Graph problem → identify if BFS or DFS
"Shortest path" → BFS (unweighted) / Dijkstra (weighted)
"Is connected?" → DFS or Union-Find
"Cycle detection" → DFS with in-progress set
"Topological order" → DFS postorder or Kahn's BFS
"Grid traversal" → DFS (flood-fill, islands) / BFS (shortest path)
Always:
Track visited to avoid infinite loops
Handle null/empty base case first
For graphs: visited set is mandatoryFound this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.