Sliding windows: decide what the window tracks before writing the loop
A window works when validity changes monotonically with its edges. Write down the tracked state and the shrink condition first, and the two-pointer loop amortises to O(n) on its own.
The hard part of a sliding window was never the two pointers. It is what the window tracks. Decide that and the nested structure collapses on its own; leave it vague and the version you write recounts the same character forever.
The template: grow right, shrink left
Take the longest substring without repeating characters. The window is [left, right] and the tracked state is the last index seen for each character. Each step of the right edge may reveal a repeat inside the window, at which point the left edge jumps past it.
function longestUnique(s) {
const seen = new Map();
let left = 0;
let best = 0;
for (let right = 0; right < s.length; right += 1) {
const ch = s[right];
const last = seen.get(ch);
if (last !== undefined && last >= left) left = last + 1;
seen.set(ch, right);
best = Math.max(best, right - left + 1);
}
return best;
}
Note the last >= left test. The character may have appeared outside the window earlier, in which case it is irrelevant to the current window and the left edge must not move. Beginners usually write seen.has(ch) and stop there.
Why it is O(n)
Both right and left only move right and never come back: right takes n steps and left takes at most n in total. So despite the nested shape, the total work is at most 2n. That relies on the shrink condition not reversing when the left edge advances.
When not to use it
If validity does not change monotonically as the window grows, meaning a larger window can be valid or invalid with no pattern, then advancing the left edge cannot restore validity and the two-pointer idea fails. Redefine the tracked state so that invalidity is monotonic, or fall back to prefix sums, a monotonic deque, or binary search.
Three variants of the same template
| Problem | Tracked state | Shrink when |
|---|---|---|
| Longest substring without repeats | Last index per character | A repeat appears |
| Minimum window substring | Missing count per character | All characters are covered |
| Maximum sum of a fixed-length subarray | Window sum | Length exceeds k |
For minimum-window problems the answer is updated inside the shrink loop, not while growing, because the shortest valid window can appear at any point during shrinking.
Validity has to be monotonic: growing the right edge moves it from valid to invalid, or the reverse, and shrinking the left edge repairs it.

Comments
…