Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

34 chapters
⌕
Beginner10›
B1Complexity analysisB2Arrays & stringsB3HashingB4Two pointersB5Sliding windowB6Binary searchB7Sorting algorithmsB8Stacks & queuesB9Linked listsB10Basic recursion
Intermediate12›
I1TreesI2Tree problems in depthI3Heaps & priority queuesI4Graphs: representationI5Graph problemsI6BacktrackingI7DP: 1DI8DP: 2DI9Greedy algorithmsI10IntervalsI11Bit manipulationI12Matrix problems
Advanced12›
A1Advanced DPA2Union-FindA3Advanced graph algorithmsA4Minimum Spanning TreeA5TriesA6Segment & Fenwick treesA7String algorithmsA8Monotonic stack & queueA9Design problemsA10Advanced backtrackingA11Topological patternsA12Interview strategy
/ search[ ] chaptert top

DSA in JS levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
I5

Graph problems

Three questions that reuse the exact same BFS/DFS you just learned, with one twist each.

Connected components — how many separate "islands" exist

component 1 (3 nodes) component 2 (2 nodes) no edges between the two groups
Run DFS/BFS from any unvisited node — everything it reaches is one component. Repeat until nothing's left unvisited.
function countComponents(n, edges) {
  const graph = Array.from({ length: n }, () => []);
  for (const [u, v] of edges) { graph[u].push(v); graph[v].push(u); }

  const visited = new Set();
  let components = 0;

  for (let node = 0; node < n; node++) {
    if (visited.has(node)) continue; // already covered by an earlier DFS
    components++;
    // flood-fill everything reachable from "node" into "visited"
    const stack = [node];
    visited.add(node);
    while (stack.length) {
      const curr = stack.pop();
      for (const next of graph[curr]) {
        if (!visited.has(next)) { visited.add(next); stack.push(next); }
      }
    }
  }
  return components;
}

The pattern generalizes directly to grid problems ("number of islands"): each land cell is a node, each adjacent land cell is an edge — same flood-fill, just walking up/down/left/right instead of an adjacency list.

Cycle detection — the rule differs by directed vs undirected

⚠ The single most common graph-interview mistake On an undirected graph, seeing a visited neighbor doesn't automatically mean a cycle — it might just be the edge you arrived from. You must track and exclude the parent explicitly. On a directed graph, that concern doesn't apply, but you now need to distinguish "visited earlier, finished" from "visited and still on the current path" — the difference between them is the whole check.
// undirected: skip the edge back to where you just came from
function hasCycleUndirected(graph, node, visited, parent) {
  visited.add(node);
  for (const next of graph[node]) {
    if (!visited.has(next)) {
      if (hasCycleUndirected(graph, next, visited, node)) return true;
    } else if (next !== parent) {
      return true; // hit an already-visited node that ISN'T where we came from
    }
  }
  return false;
}

// directed: need a THIRD state — "on the current recursion path"
function hasCycleDirected(graph, n) {
  const state = new Array(n).fill(0); // 0=unvisited, 1=in-progress, 2=done

  function dfs(node) {
    state[node] = 1;
    for (const next of graph[node]) {
      if (state[next] === 1) return true;       // back-edge to an in-progress node = cycle
      if (state[next] === 0 && dfs(next)) return true;
    }
    state[node] = 2;
    return false;
  }

  for (let i = 0; i < n; i++) {
    if (state[i] === 0 && dfs(i)) return true;
  }
  return false;
}

That three-state trick (unvisited / in-progress / done) on a directed graph is the exact same idea behind detecting a circular dependency — "in-progress" means "currently on the stack of things depending on each other," and looping back to one of those is the cycle.

Bipartite check — can you 2-color it with no clashes?

every red only connects to green — never red-to-red
Bipartite = every edge crosses between the two groups, never stays within one.
function isBipartite(graph) {
  const color = new Array(graph.length).fill(0); // 0=uncolored, 1 or -1 = the two colors

  for (let start = 0; start < graph.length; start++) {
    if (color[start] !== 0) continue;
    color[start] = 1;
    const queue = [start];

    while (queue.length) {
      const node = queue.shift();
      for (const next of graph[node]) {
        if (color[next] === 0) {
          color[next] = -color[node]; // force the opposite color
          queue.push(next);
        } else if (color[next] === color[node]) {
          return false; // a neighbor shares my color — contradiction
        }
      }
    }
  }
  return true;
}
Say it like this → "I'll BFS while alternating colors between each node and its neighbors — if I ever find an edge connecting two same-colored nodes, that's a direct proof the graph isn't 2-colorable, which is exactly what 'not bipartite' means."

Recognizing which one an unseen problem wants

  • "How many groups/islands/provinces" → connected components
  • "Can these all be completed" / "is there a circular dependency" → cycle detection (directed, usually — think course prerequisites)
  • "Can you split into two groups with no conflicts" / "is this graph 2-colorable" → bipartite check
  • All three reuse the exact same BFS/DFS skeleton from the previous chapter — the only new part is what you track while visiting
Practice this layer

Opens in the editor — write it, run it, and check it against real tests.

Pacific Atlantic Water Flow5 tests · advancedWord Ladder5 tests · advanced
←previousGraphs: representation↑ CovernextBacktracking→