Techniques
Sliding Window
Track a moving range — convert O(n²) subarray problems to O(n)
The sliding window technique maintains a contiguous range over an array or string, expanding the right edge and shrinking the left edge based on a condition. Each element enters and leaves the window at most once, giving O(n) total work for problems that brute force would solve in O(n × k) or O(n²). Used for substring problems, k-window aggregates, and rate-limiting algorithms.
- TimeO(n) — each element processed at most twice
- SpaceO(window contents)
- Two flavorsFixed-size window, variable-size window
- Vs brute forceO(n × k) → O(n) for fixed-size; O(n²) → O(n) for variable
- Famous problemsLongest substring no-repeat, max k-subarray, min window substring
- Used inRate limiters, streaming statistics, time-series aggregation
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
How sliding window works
Maintain two indices, left and right, defining the window [left, right]. The right edge advances every iteration. The left edge advances when the window violates some condition.
for right in range(n):
add arr[right] to window
while window violates constraint:
remove arr[left] from window
left += 1
update answer using current window
Total work — each element is added once (right advances n times) and removed at most once (left advances ≤ n times). O(n) amortized regardless of how often the inner shrinking loop iterates.
Fixed-size vs variable-size windows
| Fixed-size (size k) | Variable-size | |
|---|---|---|
| Window size | Always exactly k | Depends on a constraint |
| Left edge | Always lags k positions behind right | Advances only when constraint violated |
| Common questions | "What's the max/min/sum/avg over any k-window?" | "Longest/shortest subarray satisfying X" |
| Examples | Moving average, max k-subarray, max k-sliding | Longest no-repeat substring, min window substring |
| Code complexity | ~10 lines | ~20 lines |
Fixed-window: maximum sum of any k consecutive elements
function maxSumK(arr, k) {
if (arr.length < k) return null;
// Sum of first k
let windowSum = 0;
for (let i = 0; i < k; i++) windowSum += arr[i];
let maxSum = windowSum;
// Slide
for (let i = k; i < arr.length; i++) {
windowSum += arr[i] - arr[i - k]; // add new, remove old
if (windowSum > maxSum) maxSum = windowSum;
}
return maxSum;
}
The brute force recomputes each window's sum in O(k); total O(n × k). Sliding window updates incrementally — O(1) per shift, O(n) total.
Variable-window: longest substring with no repeating characters
function longestUniqueSubstring(s) {
const seen = new Map(); // char → last index seen
let left = 0, best = 0;
for (let right = 0; right < s.length; right++) {
const c = s[right];
if (seen.has(c) && seen.get(c) >= left) {
left = seen.get(c) + 1; // skip past previous occurrence
}
seen.set(c, right);
if (right - left + 1 > best) best = right - left + 1;
}
return best;
}
// "abcabcbb" → 3 ("abc")
// "bbbbb" → 1 ("b")
// "pwwkew" → 3 ("wke")
Each character is added to the map once and may cause a left jump once. O(n) total despite the inner conditional.
Variable-window: minimum window substring (LeetCode 76)
Given strings s and t, find the smallest window in s containing all characters of t (with multiplicities). The classic hard sliding-window problem.
function minWindow(s, t) {
if (s.length < t.length) return '';
const need = new Map();
for (const c of t) need.set(c, (need.get(c) || 0) + 1);
let needCount = need.size;
const have = new Map();
let haveCount = 0;
let left = 0, bestLen = Infinity, bestL = 0;
for (let right = 0; right < s.length; right++) {
const c = s[right];
if (need.has(c)) {
have.set(c, (have.get(c) || 0) + 1);
if (have.get(c) === need.get(c)) haveCount++;
}
// Shrink from left while constraint still holds
while (haveCount === needCount) {
if (right - left + 1 < bestLen) {
bestLen = right - left + 1;
bestL = left;
}
const lc = s[left];
if (need.has(lc)) {
have.set(lc, have.get(lc) - 1);
if (have.get(lc) < need.get(lc)) haveCount--;
}
left++;
}
}
return bestLen === Infinity ? '' : s.slice(bestL, bestL + bestLen);
}
Sliding window for streaming and rate limiting
Time-windowed analytics use the same pattern but indexed by timestamp instead of array position. A rate limiter counting requests per minute:
class SlidingWindowRateLimiter {
constructor(limit, windowMs) {
this.limit = limit;
this.windowMs = windowMs;
this.timestamps = new Map(); // userId → array of timestamps
}
allow(userId) {
const now = Date.now();
const cutoff = now - this.windowMs;
let arr = this.timestamps.get(userId) || [];
// Drop expired entries (slide window)
while (arr.length && arr[0] <= cutoff) arr.shift();
if (arr.length >= this.limit) {
this.timestamps.set(userId, arr);
return false;
}
arr.push(now);
this.timestamps.set(userId, arr);
return true;
}
}
Note — arr.shift() is O(n) in JavaScript. Production rate limiters use deques or sliding-counter approximations to keep operations O(1).
When to use sliding window
- Subarray-with-property questions. "Longest contiguous subarray with sum ≤ X," "longest substring with at most K distinct characters," "minimum window containing all chars of T." If the property is monotonic in window size, sliding window applies.
- Fixed-size window aggregates. Moving averages, k-element sums, max-of-k-window. Standard time-series operation.
- Rate limiting. "At most N actions in the last W seconds." Token bucket, leaky bucket, sliding window — all variants of the same core idea.
- Streaming analytics. Rolling top-k, recent unique users, anomaly detection. Window-indexed by event time.
- Two-pointer-on-string problems. Often the most natural framing.
When sliding window fails
- Non-monotonic constraints. "Longest subarray with sum exactly X" — extending can over- or under-shoot. Use prefix sums + hash map (find pair of prefix sums whose difference is X).
- Negative numbers in "sum at most X" problems. Adding a negative number can satisfy a previously-violated constraint, breaking the monotonic assumption. Use prefix sums or alternative approaches.
- Non-contiguous subsets. Sliding window only works for contiguous subarrays. "Subset with sum X" needs DP, not windowing.
- Tiny n where O(n²) is fine. The sliding-window logic adds overhead; for n < 100 the constant factors of brute force often win.
Common sliding window bugs
- Off-by-one in window size. Window is
[left, right]inclusive — size isright - left + 1. Forgetting the +1 produces "longest" answers that are 1 too short. - Not advancing left in the inner shrink loop. Forgot a
left++at the end of the inner loop = infinite loop. - Wrong shrink condition. While the constraint is violated, shrink. If only shrinks once — leaves the window in an invalid state if multiple shrinks are needed.
- Forgetting to update the answer in the right place. For longest-with-property, update inside the shrink-while loop. For longest-without-property, update outside. Confusing the two leads to off-by-one or wrong answers.
- Using sliding window on non-monotonic constraints. Some questions look like sliding-window targets but aren't ("subarray with sum exactly X"). Verify monotonicity; if not, use prefix sums + hash map or DP.
- Slow inner-data-structure operations. If the window is a Map and you call .delete() and .set() per shift, that's fine. If it's an array and you use .shift() per shift, that's O(n²). Use a deque or maintain head index.
Frequently asked questions
When should I use sliding window vs brute force?
When the brute force computes overlapping aggregates over consecutive ranges. A sum, count, or frequency map of arr[i..i+k] reuses most of the data from arr[i-1..i+k-1]. Sliding window adds the new entering element and subtracts the leaving one — O(1) per shift instead of O(k). Total: O(n) instead of O(n × k).
What's the difference between two-pointers and sliding window?
Sliding window is a specific case of two-pointers where both pointers move only forward and the region between them is the "window" being maintained. Two-pointers is the broader technique — pointers can move in any direction, at any rate. Sliding window is the specialization for "subarray with property X" problems.
How do you decide when to shrink the window?
When the window violates the constraint. For "longest substring with at most k distinct characters" — when distinct count exceeds k, advance the left edge until it drops back to k or below. The right edge advances every iteration; the left edge advances when needed. Each pointer moves O(n) total.
What's a fixed-size window?
The window is exactly k elements wide. Right edge advances; left edge follows k positions behind. Used for "max sum of k consecutive elements," "moving average," "k-subarray statistics." No expansion / contraction logic — just slide.
What's a variable-size window?
Window size depends on a condition. Right edge advances unconditionally; left edge advances only when needed (e.g., constraint violated, or shrinking would still satisfy the constraint). Used for "longest/shortest substring with property X." Most challenging sliding window problems are variable-size.
How does the technique work for streaming data?
A "tumbling" or "sliding" time window in stream processing. Each event has a timestamp; the window holds events with timestamp ≥ now − duration. Add events as they arrive; remove events older than the window. Used in rate limiting, anomaly detection, and real-time analytics. Same algorithm, time-indexed instead of array-indexed.
Can sliding window solve any subarray problem?
Only when the property is monotonic — extending the window doesn't ever fix a violation, so when violated you must shrink. "Longest subarray with sum ≤ X (positive elements)" works. "Longest subarray with sum exactly X" doesn't (extending can fix or worsen the sum). For non-monotonic properties, fall back to prefix sums + hash map or DP.