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%
B5

Sliding window

Stop re-scanning the same elements — slide the window instead.

The insight: don't recompute, adjust

A brute-force "every contiguous subarray/substring" solution recomputes each window from scratch — O(n) work, done for O(n) windows, is O(n²). Sliding window notices that consecutive windows overlap almost entirely: moving from [i, j] to [i+1, j+1] only removes one element and adds one element. Update the running answer instead of rebuilding it, and the whole scan collapses to O(n).

3 1 4 1 5 9 window, size 3 slide right one step: drop nums[left], add nums[right+1] O(1) update, not a rescan
The window's total is maintained incrementally — never recomputed from scratch.

Fixed-size window

// max sum of any window of size k — O(n) time, O(1) space
function maxSumWindow(nums, k) {
  let windowSum = 0;
  for (let i = 0; i < k; i++) windowSum += nums[i]; // build first window

  let best = windowSum;
  for (let i = k; i < nums.length; i++) {
    windowSum += nums[i] - nums[i - k]; // add new, drop old — O(1)
    best = Math.max(best, windowSum);
  }
  return best;
}

Variable-size window — the more common interview shape

Here the window grows on the right and shrinks from the left based on a condition, instead of staying a fixed size. This is the pattern behind "longest substring without repeating characters," "smallest subarray with sum ≥ target," and most "longest/shortest X satisfying Y" questions.

// longest substring with no repeated characters — O(n) time, O(min(n, alphabet)) space
function longestUniqueSubstring(s) {
  const lastSeen = new Map(); // char → most recent index
  let left = 0, best = 0;

  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    if (lastSeen.has(c) && lastSeen.get(c) >= left) {
      left = lastSeen.get(c) + 1; // jump left past the repeat
    }
    lastSeen.set(c, right);
    best = Math.max(best, right - left + 1);
  }
  return best;
}

Notice left only ever moves forward — it never resets to 0 and re-scans. That "each pointer visits each index at most once" property is why this is O(n) and not O(n²) despite looking like a nested loop conceptually.

Watch the window grow and jump, step by step

s = "abcabcbb":

rightcharrepeat in window?left jumps towindowbest
0ano0"a"1
1bno0"ab"2
2cno0"abc"3
3ayes (index 0)1"bca"3
4byes (index 1)2"cab"3
5cyes (index 2)3"abc"3
6byes (index 4)5"cb"3
7byes (index 6)7"b"3

left jumps straight to one past the repeat's last position — never one step at a time, never backward. Across all 8 steps, left moved a total of 7 positions, not 7 positions per step — that's the amortized O(n) at work.

The general variable-window template

function template(arr, condition) {
  let left = 0;
  let state = /* running total, count, or map */ 0;

  for (let right = 0; right < arr.length; right++) {
    // 1. expand: fold arr[right] into state

    while (/* state violates the condition */ false) {
      // 2. shrink: undo arr[left] from state, then left++
      left++;
    }

    // 3. update the answer using the current valid window [left, right]
  }
}
⚠ Why the shrink loop doesn't make this O(n²) It looks like a loop inside a loop, but left only ever increases and can move at most n times total across the whole run — not n times per iteration of the outer loop. Add the outer loop's n steps and the inner loop's n total steps together (not multiply) and you get O(2n) = O(n). This "amortized" argument is worth being able to say out loud in an interview.
Say it like this → "I'll maintain a window with two pointers instead of recomputing each substring — the right pointer expands the window, the left pointer only shrinks it when the condition breaks, so each index is visited a constant number of times total, giving O(n) instead of the O(n²) brute force."

See the window move

Watch left jump rather than crawl. That jump is what keeps the whole scan linear even though the window shrinks and grows.

Sliding window — longest substring without repeats
Window
Best so far

Recognizing it in an unseen problem

  • The words "contiguous subarray" or "substring" (not subsequence)
  • "Longest," "shortest," "maximum," or "minimum" over a contiguous range
  • A brute force would check every [i, j] pair — O(n²) or worse
  • The condition can be checked/updated incrementally as the window changes
Practice this layer

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

Longest Substring Without Repeating Characters5 tests · intermediateMinimum Window Substring5 tests · advancedFind All Anagrams in a String5 tests · intermediateLongest Repeating Character Replacement5 tests · advancedPermutation in String5 tests · intermediateMinimum Size Subarray Sum5 tests · intermediateFruit Into Baskets5 tests · intermediateSubarray Product Less Than K5 tests · advancedMax Consecutive Ones III5 tests · intermediateLongest Continuous Subarray With Absolute Diff <= Limit5 tests · advanced
←previousTwo pointers↑ CovernextBinary search→