// Phase 2 of 4 โ€” SDE 2 Trail

Bigger Tool, Bigger Hunt

Ugh. Week 5 to Week 8 now. Tool get bigger โ€” Graph for connected world, Dynamic Programming for smart-remember hunt, Heap/Trie/Greedy for special job. Also start touch System Design โ€” how big cave (system) built for many many hunter.

Week 5

Graph โ€” BFS, DFS, Topological Sort, Union-Find

Not simple family-tree anymore. Graph โ€” cave with many path, some path go back, some cross each other. Node connect to any other node, not just one parent-child.

โ–ถ Live Hunt: BFS vs DFS on Same Graph

Pick BFS (queue, level-by-level) or DFS (stack/recursion, deep-first) to watch cave explore.

BFS vs DFS

BFS โ€” use Queue. Explore level-by-level, ring outward. Best for shortest path (unweighted).

DFS โ€” use Stack (or recursion call-stack). Go deep one path first, backtrack when stuck. Best for path-exist, cycle-detect, topological order.

Complexity

AlgorithmTimeSpace
BFS / DFSO(V+E)O(V)
Topological SortO(V+E)O(V)
Union-Find (path compress)O(ฮฑ(n)) โ‰ˆ O(1)O(V)
// BFS โ€” level by level using Queue
function bfs(graph, start) {
  const visited = new Set([start]);
  const queue = [start];
  const order = [];
  while (queue.length) {
    const node = queue.shift();
    order.push(node);
    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }
  return order;
}

// Topological Sort โ€” order that respect dependency
function topoSort(graph, nodes) {
  const visited = new Set(), stack = [];
  function dfs(node) {
    visited.add(node);
    for (const n of graph[node] || []) if (!visited.has(n)) dfs(n);
    stack.push(node); // push after all children done
  }
  for (const n of nodes) if (!visited.has(n)) dfs(n);
  return stack.reverse();
}

// Union-Find โ€” track connected group, detect cycle in undirected graph
class UnionFind {
  constructor(n) { this.parent = Array.from({length:n}, (_, i) => i); }
  find(x) {
    if (this.parent[x] !== x) this.parent[x] = this.find(this.parent[x]); // path compression
    return this.parent[x];
  }
  union(a, b) { this.parent[this.find(a)] = this.find(b); }
}
Cave wisdom: "Shortest path, unweighted" โ†’ BFS. "Does path exist / detect cycle / order tasks by dependency" โ†’ DFS or Topological Sort. "Group hunter into tribe, check if two hunter same tribe" โ†’ Union-Find.
Week 6

Dynamic Programming โ€” Fibonacci to Knapsack

Recursion waste time re-solve same small problem again and again. DP remember answer first time (memo), never solve same small problem twice. Big speed gain.

โ–ถ Live Hunt: 0/1 Knapsack Table Fill

Items: (weight 1, value 6), (weight 3, value 10), (weight 4, value 12). Bag capacity = 5.

Press Step to fill DP table cell by cell. Row = item considered, Col = capacity used.

Two Style of DP

Top-Down (Memoization) โ€” recursion + cache. Natural to write, still use call-stack.

Bottom-Up (Tabulation) โ€” build table from small to big, no recursion, often less memory.

How Spot DP Problem

Ask: 1) Can big problem break to overlapping smaller problem? 2) Does optimal answer built from optimal smaller answer (optimal substructure)? If yes to both โ€” DP candidate.

// Fibonacci โ€” bottom-up tabulation (start easy)
function fibDP(n) {
  const dp = [0, 1];
  for (let i = 2; i <= n; i++) dp[i] = dp[i-1] + dp[i-2];
  return dp[n];
}

// 0/1 Knapsack โ€” grow to hard
function knapsack(weights, values, capacity) {
  const n = weights.length;
  const dp = Array.from({length:n+1}, () => new Array(capacity+1).fill(0));

  for (let i = 1; i <= n; i++) {
    for (let w = 0; w <= capacity; w++) {
      if (weights[i-1] <= w) {
        dp[i][w] = Math.max(
          dp[i-1][w],                                   // skip item
          values[i-1] + dp[i-1][w - weights[i-1]]   // take item
        );
      } else {
        dp[i][w] = dp[i-1][w];  // too heavy, must skip
      }
    }
  }
  return dp[n][capacity];
}
Cave wisdom: Draw the DP table by hand on paper BEFORE code. Once table make sense, code just translate table rule into loop.
Week 7

Heap

Pile of stone, but special rule โ€” biggest (or smallest) stone always sit on top, easy grab. Not fully sorted, just top always correct.

Complexity

OperationTime
Get min/max (peek)O(1)
InsertO(log n)
Remove min/maxO(log n)
Build heap from arrayO(n)

When Use Heap

"Kth largest/smallest," "top K frequent," "merge K sorted list," Dijkstra shortest path, median-finder, task scheduler by priority.

// Min-Heap core operation (array-based)
class MinHeap {
  constructor() { this.heap = []; }
  peek() { return this.heap[0]; }
  push(val) {
    this.heap.push(val);
    let i = this.heap.length - 1;
    while (i > 0) {
      const parent = Math.floor((i-1)/2);
      if (this.heap[parent] <= this.heap[i]) break;
      [this.heap[parent], this.heap[i]] = [this.heap[i], this.heap[parent]];
      i = parent;
    }
  }
  // pop() would "bubble down" the last element to restore heap order โ€” similar idea reversed
}
Cave wisdom: JS have no built-in heap โ€” usually simulate with sorted insert for small n, or implement own class, or use library in real project.
Week 7

Trie (Prefix Tree)

Tree built from letter, not number. Each path from root spell out word. Share common prefix โ€” "cat" and "car" share "ca" branch.

Complexity

OperationTime
Insert wordO(L)
Search wordO(L)
Prefix searchO(L)

L = length of word. No depend on how many word stored!

When Use Trie

Autocomplete, spell-checker, IP routing table, word-search game, "longest common prefix" problem.

class TrieNode {
  constructor() { this.children = {}; 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;
  }
}
Cave wisdom: Trie trade memory for speed on prefix-heavy word problem. If problem mention "prefix" or "autocomplete" โ€” think Trie first.
Week 7

Greedy

Hunter grab best-looking stone right now, never look back, never change mind. Fast, but only work if "best now" always lead to "best overall."

Greedy vs DP

Greedy make one choice, move on, never reconsider โ€” fast but only correct for special-structure problem.

DP consider all choice, keep best โ€” slower but always correct if overlapping subproblem exist.

Classic Greedy Problem

Activity selection (max non-overlap meeting), coin change (with canonical coin system), Huffman encoding, minimum spanning tree (Kruskal/Prim โ€” use Union-Find!).

// Activity Selection โ€” max number of non-overlapping meeting
function maxActivities(activities) {
  // sort by end time โ€” greedy rule: always pick earliest-finishing next
  activities.sort((a, b) => a.end - b.end);
  let count = 1, lastEnd = activities[0].end;
  for (let i = 1; i < activities.length; i++) {
    if (activities[i].start >= lastEnd) {
      count++;
      lastEnd = activities[i].end;
    }
  }
  return count;
}
Cave wisdom: Before trust greedy, try prove (or at least sanity-test) that local-best choice never block global-best answer. If not sure โ€” DP safer.
Week 8

Big-O Review

Stop. Look back all tool learned. Big-O โ€” how hunt-time grow when pile of stone grow bigger. This week: no new tool, just sharpen eye for speed.

Complexity Ladder (Fast โ†’ Slow)

Big-ONameExample Tool
O(1)ConstantHash lookup, array index
O(log n)LogarithmicBinary search, balanced BST
O(n)LinearSingle loop, linear search
O(n log n)LinearithmicMerge sort, heap sort
O(nยฒ)QuadraticNested loop, bubble sort
O(2โฟ)ExponentialNaive recursion (no memo)
Cave wisdom: In interview, ALWAYS say time and space complexity out loud, even if not asked. Show you think about it โ€” this what separate SDE 2 hunter from SDE 1 hunter.
System Design

Load Balancer

Too many hunter come to one small cave-entrance, jam happen. Load Balancer stand at front door, send each hunter to different entrance, spread the crowd even.

Round Robin

Send request to server 1, 2, 3, 1, 2, 3... in turn. Simple, fair if all server equal strength.

Least Connections

Send to server currently handle fewest active request. Better when request-time vary a lot.

Consistent Hashing

Same key (user-id) always land same server (mostly). Good for cache-affinity, minimize reshuffle when server added/removed.

Cave wisdom: Load Balancer also do health-check โ€” stop sending hunter to sick (down) server automatic. This called "failover."
System Design

Caching

Instead walk far storage-cave every time, keep copy of popular stone near front door (cache). Much faster grab, but must remember to update if real stone change.

Cache Eviction Policy

LRU (Least Recently Used) โ€” throw out stone not touched longest time.
LFU (Least Frequently Used) โ€” throw out stone touched fewest time.
TTL (Time To Live) โ€” stone auto-expire after set time.

Cache Pattern

Cache-aside โ€” app check cache first, if miss, fetch DB then fill cache.
Write-through โ€” write go to cache AND DB together, always consistent, bit slower write.
Write-back โ€” write go to cache first, DB update later (async), fast but risk data loss.

Cave wisdom: You already touch Redis in Node.js work โ€” good foundation! In system design interview, always mention WHICH eviction policy and WHICH write pattern you pick, and why.
System Design

Database Index

Without index, database must walk every single row to find answer โ€” like search whole cave stone-by-stone. Index โ€” like table-of-content at cave entrance, jump straight to right shelf.

How Index Work

Most index use B-Tree structure underneath โ€” similar idea like balanced BST you learned Phase 1! Lookup become O(log n) instead of O(n) full table scan.

Trade-off

Index speed up READ (SELECT with WHERE/ORDER BY on indexed column), but slow down WRITE (INSERT/UPDATE/DELETE must also update index) and use extra storage.

Cave wisdom: Don't index every column blindly! Index column that appear often in WHERE, JOIN, or ORDER BY. Too many index = slow write, wasted space.
System Design

Vertical vs Horizontal Scale

Cave getting crowded โ€” two choice. Make one cave bigger (vertical), or build more cave side by side (horizontal).

Vertical Scale (Scale Up)

Add more CPU/RAM to same machine. Simple โ€” no code change needed. But hit hard ceiling (machine can only grow so big), and single point of failure remain.

Horizontal Scale (Scale Out)

Add more machine, share load between them (need Load Balancer!). No real ceiling, better fault-tolerance. But need design app to be stateless, more complex.

AspectVerticalHorizontal
ComplexityLowHigh
Cost curveExponential at top-endLinear-ish
Fault toleranceSingle point of failureRedundant
CeilingHardware limitPractically none
Cave wisdom: Real big company (AgentOS-scale dream!) almost always end up horizontal eventually. But many startup start vertical first โ€” simpler, cheaper, "good enough" until real traffic hit.