Variable window: expand right freely, shrink left when constraint is violated.
State tracking: HashMap, frequency counter, or running sum to maintain window state incrementally.
Replaces O(n²) brute force with O(n) single-pass on streaming data, log analysis, and rate limiting.
Each element enters and exits the window at most once — this is what makes it O(n).
Forgetting Math.max on the left boundary in variable windows. Without it, the left pointer jumps backward when a duplicate was seen before the current window, silently producing wrong answers.
✦ Definition~90s read
What is Sliding Window Technique?
This article addresses a specific, subtle bug in fixed-window rate limiters: the ability to burst up to 2x the configured limit at window boundaries. A fixed-window rate limiter (e.g., 100 requests per minute) resets its counter at the start of each minute.
★
Imagine looking at a parade through a camera viewfinder that slides along the street.
If a client sends 100 requests in the last second of minute 1, then 100 more in the first second of minute 2, the actual throughput over a 2-second span is 200 requests — double the intended limit. This is not a theoretical edge case; it's a common exploit in production systems using naive implementations like a simple Redis counter with TTL.
The article then pivots to the sliding window technique, which solves this by considering a continuous time window (e.g., the last 60 seconds) rather than discrete buckets. It explains the concept through the classic 'Maximum Sum Subarray of Size K' problem, then extends to variable-length windows for problems like 'Longest Substring Without Repeating Characters' and 'At Most K Distinct Characters.' The core insight: sliding windows maintain a dynamic range that adjusts to data, preventing the boundary burst issue and enabling efficient O(n) solutions for substring/subarray problems where fixed windows fail.
Plain-English First
Imagine looking at a parade through a camera viewfinder that slides along the street. You never re-examine floats that have passed — you just note the new float entering the right side and forget the one leaving the left side. At any moment, your viewfinder shows a contiguous segment of the parade. Sliding window is exactly that: a fixed-size or constraint-sized viewport that moves across your data, updating its contents incrementally instead of recomputing from scratch.
Subarray and substring problems dominate coding interviews and production systems alike. Finding the maximum sum of k consecutive elements, detecting rate limit violations in a time window, or identifying the longest substring without repeating characters — all of these share the same structure: a contiguous window of data that must be evaluated efficiently.
The naive approach checks every possible window from scratch — O(n²) or worse. Sliding window eliminates this by maintaining the window state incrementally. When the window slides one position to the right, you add the new element and remove the old one in O(1), rather than recomputing the entire window. This single insight converts O(n*k) brute force into O(n) single-pass.
Two flavors exist: fixed-size windows (constant k) and variable-size windows (expand/contract based on a constraint). Both share the invariant that each element enters and exits the window at most once. Recognizing which flavor applies — and correctly implementing the constraint logic — is the difference between a correct solution and a silent wrong answer.
Why Fixed Window Rate Limiting Leaks 2x Traffic at Boundaries
The sliding window technique solves a specific failure: fixed window rate limiters allow up to 2x burst traffic at window boundaries. A fixed window resets counters at rigid intervals (e.g., every second), so a client can send N requests at 0:00.999 and another N at 0:01.001 — effectively doubling throughput in a 2ms span. Sliding window replaces the hard reset with a continuous, rolling time frame, typically implemented via a sorted log of timestamps or a sliding counter that weights partial windows.
In practice, sliding window uses O(n) memory per key (timestamp log) or O(1) with a hybrid approach: track the current window count and the previous window's count, then interpolate. The formula is: weight = (current_time - window_start) / window_size; estimated_count = previous_count * (1 - weight) + current_count. This gives sub-second granularity without per-request storage. The tradeoff is slight approximation — exact sliding windows require full logs, but most production systems accept <5% error for constant memory.
Use sliding window wherever burst tolerance must be bounded: API rate limiting, login throttling, or any resource where fixed windows cause predictable abuse. It’s the standard for production gateways (Kong, Envoy) because it prevents the “midnight madness” pattern — clients learning to hammer exactly at reset boundaries. Without it, your rate limiter is a sieve.
Approximation vs. Exactness
Sliding window with partial counts is an estimate — it can allow up to ~1.5x burst if previous window was full. For strict limits, use a sorted set with O(log n) eviction.
Production Insight
Real scenario: An API gateway using 1-second fixed windows saw 2x traffic spikes at :00.000 as clients synchronized on reset. Symptom: backend CPU spiked every second, causing cascading timeouts. Rule: Always use sliding window for any rate limiter exposed to adversarial or bursty clients — fixed windows are only safe for internal, trusted callers.
Key Takeaway
Fixed windows allow 2x burst at boundaries — sliding window eliminates the reset gap.
Sliding window with interpolation is O(1) memory but approximate; exact sliding window costs O(n) per key.
Always use sliding window for external-facing rate limiters; fixed windows are a footgun in production.
thecodeforge.io
Fixed Window Rate Limiter Bug — 2x Burst at Boundaries
Sliding Window Technique
Worked Example — Maximum Sum Subarray of Size K
Find the maximum sum subarray of size k=3 in arr=[2,1,5,1,3,2].
For variable-size windows (e.g., longest substring with at most k distinct chars): expand right pointer, and when the constraint is violated, shrink by advancing the left pointer until valid again. The window always contains a valid subarray. Each element enters and leaves the window at most once, giving O(n) time.
package io.thecodeforge.algo;
publicclassMaxSumSubarrayFixedWindow {
/**
* Finds the maximum sum of any contiguous subarray of size k.
* Fixed-size sliding window — O(n) time, O(1) space.
*/
publicstaticintmaxSumSubarray(int[] arr, int k) {
if (arr.length < k) {
thrownewIllegalArgumentException("Array length must be >= k");
}
// Compute sum of first windowint windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += arr[i];
}
int maxSum = windowSum;
// Slide the window: add right element, remove left elementfor (int i = k; i < arr.length; i++) {
windowSum += arr[i]; // add incoming element at right edge
windowSum -= arr[i - k]; // remove outgoing element at left edge
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
publicstaticvoidmain(String[] args) {
int[] arr = {2, 1, 5, 1, 3, 2};
System.out.println("Maxsum (k=3): " + maxSumSubarray(arr, 3)); // 9 — [5,1,3]System.out.println("Maxsum (k=2): " + maxSumSubarray(arr, 2)); // 6 — [5,1]System.out.println("Maxsum (k=4): " + maxSumSubarray(arr, 4)); // 10 — [5,1,3,2]// Trace the window slides for k=3:// [2,1,5] sum=8 max=8// [1,5,1] sum=7 max=8 (removed 2, added 1)// [5,1,3] sum=9 max=9 (removed 1, added 3)// [1,3,2] sum=6 max=9 (removed 5, added 2)
}
}
Output
Max sum (k=3): 9
Max sum (k=2): 6
Max sum (k=4): 10
Why Each Element Is Visited Exactly Twice
Initial window: compute sum of first k elements. O(k).
Each slide: +arr[i] - arr[i-k]. O(1). Total slides: n-k. Total: O(n).
Brute force: recompute sum of k elements at each position. O(n*k).
Sliding window speedup: kx. For k=1000, that is 1000x faster.
Invariant: window_sum always equals sum(arr[i-k+1..i]). Verify this mentally on each slide.
Production Insight
A monitoring system computed the average CPU utilization over a 5-minute sliding window (300 data points at 1-second intervals) every second. The naive approach summed all 300 points at each step — O(300n) = O(n*k). With 10,000 servers, this was 3 million additions per second. Switching to a sliding window (add new point, subtract old point) reduced it to 10,000 additions and 10,000 subtractions per second — a 150x reduction. CPU usage on the monitoring service dropped from 40% to 0.3%.
Key Takeaway
Fixed-size sliding window replaces k additions per slide with 1 addition and 1 subtraction. Each element is visited exactly twice. The speedup factor is k — for k=300, that is 300x faster than brute force.
How Sliding Window Works — Plain English and Step-by-Step
A sliding window maintains a contiguous subarray or substring and slides it across the input to avoid O(n^2) brute force.
Fixed-size window (size k) — max sum subarray: 1. Compute sum of first k elements. 2. For each new position i from k to n-1: add arr[i], subtract arr[i-k]. Update max. O(n) total — each element is added and removed exactly once.
Variable-size window — longest substring without repeating characters: 1. Use left and right pointers, both starting at 0. Maintain a seen dict of last index. 2. Expand right: for each char, if it was seen at index >= left, move left to seen[char]+1. 3. Update seen[char] = right. Record window length right-left+1 if it's a new maximum.
package io.thecodeforge.algo;
/**
* Summary of the two sliding window flavors.
* Both achieve O(n) by visiting each element at most twice.
*/
publicclassSlidingWindowPatternSummary {
/*
* FIXED-SIZEWINDOW
* ─────────────────
* Setup: compute sum/state of first k elements.
* Slide: add arr[right], subtract arr[right-k]. Update result.
* Constraint: window size is always exactly k.
* Time: O(n), Space: O(1)
* Examples: max sum subarray of size k, moving average, max of all subarrays of size k.
*/
/*
* VARIABLE-SIZEWINDOW
* ────────────────────
* Setup: left=0, right=0, empty state.
* Expand: advance right, add arr[right] to state.
* Shrink: while constraint violated, remove arr[left], advance left.
* Constraint: dynamically maintained (distinct count, sum threshold, etc.)
* Time: O(n), Space: O(k) where k is constraint size
* Examples: longest substring without repeating, min window substring, longest subarray with sum <= target.
*/
publicstaticvoidmain(String[] args) {
System.out.println("Sliding Window Flavors:");
System.out.println("1. Fixed-size — window is always k elements. Add right, subtract left.");
System.out.println("2. Variable-size — expand right, shrink left when constraint violated.");
System.out.println("\nBoth: each element enters and exits at most once. O(n) total.");
}
}
Output
Sliding Window Flavors:
1. Fixed-size — window is always k elements. Add right, subtract left.
2. Variable-size — expand right, shrink left when constraint violated.
Both: each element enters and exits at most once. O(n) total.
The Monotone Constraint Requirement
Monotone: 'at most k distinct chars' — adding a new char makes it more invalid. Shrinking fixes it.
Non-monotone: 'exactly k distinct chars' — adding a char can make it valid OR invalid. Use atMost(k) - atMost(k-1).
Monotone: 'sum <= target' — adding a large value can violate the constraint. Shrinking restores it.
Non-monotone: 'sum == target' — adding can overshoot or undershoot. Use prefix sum + HashMap instead.
Rule: if shrinking the window always moves it closer to valid the window 'more invalid', removing makes it 'more valid, sliding window applies.
Production Insight
A team tried to use sliding window for 'find the shortest subarray with exactly k distinct elements'. The constraint 'exactly k' is non-monotone — adding a new distinct element could make the window valid (if it was at k-1) or invalid (if it was already at k). The sliding window implementation produced wrong answers. The fix: compute longestSubstringWithAtMostKDistinct(k) - longestSubstringWithAtMostKDistinct(k-1). Both sub-problems use monotone constraints and work correctly with sliding window.
Key Takeaway
Sliding window requires a monotone constraint: adding elements makes the window more invalid, removing makes it more valid. Non-monotone constraints (exactly k, sum equals target) require conversion tricks or different algorithms.
Fixed Window — Maximum Sum Subarray of Size K
The fixed-size window, 1, 3, 2}; maintains a running sum. After the initial window is computed, each subsequent step adds the new right element and removes the element that left the window — both in O(1). This achieves O(n) overall versus O(n*k) brute force. The same pattern applies to maximum/minimum, average, or any aggregation over a window of fixed size.
package io.thecodeforge.algo;
publicclassMaxSumSubarrayFixedWindow {
publicstaticintmaxSumSubarray(int[] arr, int k) {
if (arr.length < k) {
thrownewIllegalArgumentException("Array length must be >= k");
}
int windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += arr[i];
}
int maxSum = windowSum;
for (int i = k; i < arr.length; i++) {
windowSum += arr[i];
windowSum -= arr[i - k];
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
publicstaticvoidmain(String[] args) {
int[] arr = {2, 1, 5System.out.println(maxSumSubarray(arr, 3)); // 9System.out.println(maxSumSubarray(arr, 2)); // 6
}
}
Output
9
6
Pro Tip: Generalize Beyond Sums
Sum: running total. Add right, subtract left. O(1) per slide.
Average: running sum / k. O(1) per slide.
Min/Max: monotonic deque. O(1) amortized per slide.
Count matching predicate: add 1 if right matches, subtract 1 if left matches. O(1) per slide.
Median: two heaps (max-heap for lower half, min-heap for upper half). O(log k) per slide.
Production Insight
A financial dashboard computed the maximum trading volume over every 30-minute window across 5,000 stocks. With 6.5 hours of trading data (390 minutes) per stock, there were 361 windows per stock. The naive O(nk) approach did 361 30 = 10,830 comparisons per stock. With 5,000 stocks, that was 54 million comparisons per day. Using a monotonic deque for the sliding window max reduced it to 390 operations per stock (each element enters and exits the deque once). Total: 1.95 million operations — a 28x reduction.
Key Takeaway
Fixed window replaces k operations per slide with 1 add and 1 subtract. For min/max aggregation, use a monotonic deque for O(1) per slide instead of O(k) scan. The pattern generalizes to any aggregation function.
Variable Window — Longest Substring Without Repeating Characters
The variable window expands the right pointer unconditionally and shrinks the left pointer only when the constraint is violated. A hash map tracks the last seen index of each character. When a duplicate is encountered, move left to max(left, last_seen[char]+1) to skip past it — the max ensures left never moves backward. Update last_seen[char] after the left adjustment.
package io.thecodeforge.algo;
import java.util.HashMap;
import java.util.Map;
publicclassLongestUniqueSubstring {
/**
* Finds the longest substring with no repeating characters.
* Variable-size sliding window with HashMap tracking last-seen index.
* O(n) time, O(min(n, alphabet)) space.
*/
publicstaticStringlongestNonRepeating(String text) {
Map<Character, Integer> lastSeenAtIndex = newHashMap<>();
int maxLength = 0;
int windowStart = 0;
int bestWindowStart = 0;
for (int windowEnd = 0; windowEnd < text.length(); windowEnd++) {
char currentChar = charAt(windowEnd, text);
// If char was seen AND is inside current window, shrink left past itif (lastSeenAtIndex.containsKey(currentChar)
&& lastSeenAtIndex.get(currentChar) >= windowStart) {
windowStart = lastSeenAtIndex.get(currentChar) + 1;
}
lastSeenAtIndex.put(currentChar, windowEnd);
int currentLength = windowEnd - windowStart + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
bestWindowStart = windowStart;
}
}
return text.substring(bestWindowStart, bestWindowStart + maxLength);
}
privatestaticcharcharAt(int index, String text) {
return text.charAt(index);
}
publicstaticvoidmain(String[] args) {
String[] tests = {"abcabcbb", "bbbbb", "pwwkew", "thecodeforge"};
for (String input : tests) {
String result = longestNonRepeating(input);
System.out.printf("Input: %-15s → \"%s\" (length %d)%n",
"\"" + input + "\"", result, result.length());
}
}
}
Output
Input: "abcabcbb" → "abc" (length 3)
Input: "bbbbb" → "b" (length 1)
Input: "pwwkew" → "wke" (length 3)
Input: "thecodeforge" → "thecod" (length log messages per second6)
The Math.max Guard Is Mandatory
Without Math.max: windowStart jumps backward on characters seen before the current window.
With Math.max: windowStart only moves forward. The window invariant is preserved.
Test cases that catch this bug: 'abba' (expected 2), 'tmmzuxt' (expected 5).
The guard ensures each character is visited at most twice — once by right, once by left.
This is the #1 bug in sliding window implementations in interviews.
Production Insight
A real-time log deduplication service used sliding window to find the longest sequence of unique log messages in a 10-second window. Without the Math.max guard, the service reported windows of 50,000 unique messages when the actual maximum was 12. The bug was invisible in testing (small inputs) but manifested in production with 100,000+ log messages per second. The fix: add Math.max(windowStart, lastSeen.get(messageHash) + 1). The service correctly reported the longest unique sequence after the fix.
Key Takeaway
Variable window expands right freely and shrinks left when the constraint is violated. The Math.max guard on the left boundary is mandatory — without it, the window silently grows incorrectly on repeated elements. Test with 'abba' and 'tmmzuxt' to catch this bug.
Variable Window — At Most K Distinct Characters
The variable window expands the right pointer unconditionally and shrinks the left pointer only when the constraint is violated. A frequency map tracks the count of each character in the window. When the number of distinct characters exceeds k, shrink from the left by decrementing the left character's frequency and removing it from the map when the count reaches 0.
package io.thecodeforge.algo;
import java.util.HashMap;
import java.util.Map;
publicclassLongestSubstringKDistinct {
/**
* Finds the longest substring with at most k distinct characters.
* Variable-size sliding window with frequency map.
* O(n) time, O(k) space.
*/
publicstaticintlongestSubstringKDistinct(String s, int k) {
if (k == 0) return0;
Map<Character, Integer> freq = newHashMap<>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < s.length(); right++) {
char rightChar = s.charAt(right);
freq.merge(rightChar, 1, Integer::sum);
// Shrink window while constraint is violatedwhile (freq.size() > k) {
char leftChar = s.charAt(left);
freq.merge(leftChar, -1, Integer::sum);
if (freq.get(leftChar) == 0) {
freq.remove(leftChar);
}
left++;
}
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
/**
* Converts'exactly k distinct' to 'at most k' minus 'at most k-1'.
* This is the standard trick for non-monotone constraints.
*/
publicstaticintexactlyKDistinct(String s, int k) {
returnlongestSubstringKDistinct(s, k) - longestSubstringKDistinct(s, k - 1);
}
publicstaticvoidmain(String[] args) {
System.out.println("At most 2 distinct in 'eceba': " + longestSubstringKDistinct("eceba", 2)); // 3System.out.println("At most 1 distinct in 'aa': " + longestSubstringKDistinct("aa", 1)); // 2System.out.println("At most 3 distinct in 'aabacbebebe': " + longestSubstringKDistinct("aabacbebebe", 3)); // 7System.out.println("Exactly2 distinct in 'eceba': " + exactlyKDistinct("eceba", 2)); // 3System.out.println("Exactly3 distinct in 'eceba': " + exactlyKDistinct("eceba", 3)); // 4
}
}
Output
At most 2 distinct in 'eceba': 3
At most 1 distinct in 'aa': 2
At most 3 distinct in 'aabacbebebe': 7
Exactly 2 distinct in 'eceba': 3
Exactly 3 distinct in 'eceba': 4
Pro Tip: The 'At Most K' Trick for Exactly K
atMost(k): sliding window, O(n). Shrink when distinct > k.
Why it works: atMost(k) counts all substrings with 1, 2, ..., k distinct. Subtract atMost(k-1) to isolate exactly k.
Alternative: maintain both count and distinct in the window. But this is harder to get right.
This trick generalizes: exactly k odd numbers = atMost(k) - atMost(k-1).
Production Insight
An analytics platform needed to find segments of user sessions with exactly 3 product categories browsed. The naive approach checked every possible segment — O(n²). Using the atMost(k) - atMost(k-1) trick with sliding window, each atMost call was O(n), giving O(n) total. Processing 50 million session events per day took 12 seconds instead of the estimated 6 hours with brute force.
Key Takeaway
Variable window with frequency map tracks distinct count incrementally. For 'exactly k' constraints, use the atMost(k) - atMost(k-1) trick to convert non-monotone constraints into monotone ones. Each element enters and exits the window at most once.
Sliding Window Minimum — The Monotonic Deque Pattern
Finding the maximum or minimum of every window of size k is a classic problem. The naive approach scans all k elements in each window — O(n*k). A monotonic deque maintains candidates in sorted order, giving O(1) amortized per slide.
For maximum: maintain a deque of indices where values are in decreasing order. The front of the deque is always the maximum of the current window. When sliding, remove the front if it's outside the window, and remove all elements from the back that are smaller than the new element (they can never be the maximum while the new element is in the window).
package io.thecodeforge.algo;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
publicclassSlidingWindowMaximum {
/**
* Finds the maximum element in every window of size k.
* Uses a monotonic deque — O(n) time, O(k) space.
* Each element enters and exits the deque at most once.
*/
publicstaticint[] maxSlidingWindow(int[] arr, int k) {
if (arr.length < k || k <= 0) returnnewint[0];
int[] result = newint[arr.length - k + 1];
Deque<Integer> deque = new ArrayDeque<>(); // stores indices, values in decreasing orderfor (int i = 0; i < arr.length; i++) {
// Remove indices outside the current windowwhile (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// Remove all elements smaller than arr[i] from the back// They can never be the max while arr[i] is in the windowwhile (!deque.isEmpty() && arr[deque.peekLast()] < arr[i]) {
deque.pollLast();
}
deque.offerLast(i);
// Window is complete — record the max (front of deque)if (i >= k - 1) {
result[i - k + 1] = arr[deque.peekFirst()];
}
}
return result;
}
publicstaticvoidmain(String[] args) {
int[] arr = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = maxSlidingWindow(arr, k);
System.out.println("Array: " + Arrays.toString(arr));
System.out.println("Max in each window of size " + k + ": " + Arrays.toString(result));
// [3, 3, 5, 5, 6, 7]
}
}
Output
Array: [1, 3, -1, -3, 5, 3, 6, 7]
Max in each window of size 3: [3, 3, 5, 5, 6, 7]
Why the Monotonic Deque Works
Deque stores indices, not values. Values are looked up via arr[index].
Decreasing order: front is always the maximum of the current window.
Remove from front: index is outside the window (i - k + 1).
Remove from back: value is smaller than the new element. It is dominated.
Each element enters once and exits once. Total: O(n) amortized.
Production Insight
A stock trading platform displayed the highest price in every 15-minute window across 2,000 stocks. The naive O(n*k) approach with k=900 (15 minutes of 1-second data) was 900 comparisons per window. With 6.5 hours of data per stock (23,400 windows), that was 21 million comparisons per stock. The monotonic deque reduced it to ~46,800 operations per stock (each of the 23,400 data points enters and exits the deque once). Total savings: 450x across all stocks.
Key Takeaway
Monotonic deque maintains the window's max/min in O(1) amortized per slide. Each element enters and exits the deque at most once. This is the correct approach for 'max/min of every window of size k' — never use a naive O(k) scan.
How to Identify Sliding Window Problems — Before Your Interviewer Smells Blood
You don't need a checklist. You need pattern recognition. Every Sliding Window problem screams with the same tell: you're asked to process a contiguous sequence where the naive solution walks the same ground twice.
Look for three signals. First, the problem mentions subarray, substring, or window — that's the freebie. Second, the brute force O(n²) or O(n*k) solution feels obvious but painful. You're summing the same elements, counting the same characters, checking the same boundaries. Third — and this is the real tell — the solution should reuse a previous computation without re-scanning what's left behind.
Fixed window problems always give you k. Variable window problems never do — they ask for longest, shortest, or at-most constraints. If you see "maximum sum of any contiguous subarray" with no size, that's Kadane's, not sliding window. If you see "longest substring with at most K distinct characters" — that's your sliding window, and your window pointer just heard the starting gun.
SlidingWindowDetector.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — dsa tutorialpublicclassSlidingWindowDetector {
// Hueristic: count the clues before codingpublicstaticbooleanisSlidingWindow(String problemStatement) {
String lower = problemStatement.toLowerCase();
boolean hasSubarrayOrSubstring = lower.contains("subarray") || lower.contains("substring") || lower.contains("window");
boolean hasSizeConstraint = lower.contains("size k") || lower.contains("at most") || lower.contains("longest") || lower.contains("shortest");
boolean hasContiguousRequirement = lower.contains("contiguous") || lower.contains("continuous") || lower.contains("consecutive");
return hasSubarrayOrSubstring && (hasSizeConstraint || hasContiguousRequirement);
}
publicstaticvoidmain(String[] args) {
String test = "Find the longest substring with at most K distinct characters";
System.out.println("Is sliding window? " + isSlidingWindow(test)); // true
}
}
Output
Is sliding window? true
Senior Shortcut:
If the brute force iterates over every start index and then walks k steps forward, it's a sliding window candidate. The interviewer is waiting for you to say 'We're recomputing overlapping work.' Say it out loud.
Key Takeaway
Three signals: contiguous sequence, overlapping computation in brute force, and a constraint that limits the window (either fixed k or variable at-most). Always name the brute force waste before you propose the window.
Types of Sliding Window — Fixed vs. Variable, and Why Most Self-Taught Devs Get the Second Wrong
There are exactly two flavors, and they map directly to how you move the left pointer. Fixed window: left and right move in lockstep. You advance both by 1 each iteration. The window size never changes. Classic case: maximum sum subarray of size k. You add the new element, subtract the one that fell out, done.
Variable window: this is where careers stall. The right pointer grows until the constraint breaks (e.g., too many distinct characters), then you shrink from the left until the constraint is satisfied again. The window size fluctuates. The mistake juniors make? Shrinking one step at a time when they should be shrinking in a while loop. Or worse, resetting the entire window. The left pointer is the only way to maintain O(n) — every element enters once and exits once.
There's a third case you'll see in some problem sets: monotonic deque for sliding window minimum/maximum. That's not a different type of window — it's a different data structure inside the same variable-window pattern. Don't let the fancy name fool you. It's still just moving two pointers and maintaining an invariant.
SlidingWindowTypes.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// io.thecodeforge — dsa tutorialpublicclassSlidingWindowTypes {
// Fixed: window size locked at kpublicstaticintmaxSumFixed(int[] data, int k) {
if (data.length < k) return0;
int sum = 0;
for (int i = 0; i < k; i++) sum += data[i];
int max = sum;
for (int i = k; i < data.length; i++) {
sum += data[i] - data[i - k]; // slide right, drop left
max = Math.max(max, sum);
}
return max;
}
// Variable: right grows, left shrinks under constraintpublicstaticintmaxDistinctChars(String s, int maxDistinct) {
int[] freq = newint[256];
int left = 0, count = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
if (freq[s.charAt(right)]++ == 0) count++;
while (count > maxDistinct) {
if (--freq[s.charAt(left++)] == 0) count--;
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
publicstaticvoidmain(String[] args) {
System.out.println("Fixed max: " + maxSumFixed(newint[]{1,2,3,4,5}, 3));
System.out.println("Variable max: " + maxDistinctChars("aabbcc", 2));
}
}
Output
Fixed max: 12
Variable max: 4
Production Trap:
Variable window problems often translate to real-time stream processing where your 'window' is a time interval, not a size. If your left pointer doesn't move in O(1) — using a frequency map or a queue — you'll leak memory in production. The while loop inside a for loop isn't O(n²) if every element is processed by the while loop at most once. Prove it to yourself with an invariant counter.
Key Takeaway
Fixed window: left and right move together, size constant. Variable window: right expands until constraint breaks, left shrinks until constraint heals. Both are O(n) because each element enters once and exits once.
Sliding Window Minimum — The Monotonic Deque Pattern That Filters Out Weak Engineers
You've mastered sum and count. Now the interviewer hits you with: "Given an array and window size k, return the minimum in each window." The naive O(n*k) will get you ghosted. The sliding window minimum requires a different beast — a monotonic deque that maintains indices, not values.
Here's the why before the how: When a new element enters the window, any element to its left that is larger will never be the minimum again, because the new element is both smaller and will outlive them. So you pop from the back while the back's value >= current. Then push the current index. Then pop from the front if the index is out of the window. The front of the deque is always the minimum for the current window.
This trick works because you're not just sliding a window — you're maintaining a future-ranking of candidates. Each element is added once and removed once. O(n) total. The deque is your hit list. If you're asked for maximum, flip the comparison. Same pattern. Learn it once, use it for sliding window max, stock span, and every other "nearest greater element" variant that looks unrelated but isn't.
SlidingWindowMinDeque.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// io.thecodeforge — dsa tutorialimport java.util.ArrayDeque;
import java.util.Deque;
publicclassSlidingWindowMinDeque {
publicstaticint[] minSlidingWindow(int[] nums, int k) {
if (nums == null || k <= 0) returnnewint[0];
int n = nums.length;
int[] result = newint[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>(); // stores indices, not valuesfor (int i = 0; i < n; i++) {
// remove indices out of current window (from front)if (!deque.isEmpty() && deque.peekFirst() < i - k + 1)
deque.pollFirst();
// remove from back: any larger element won't be min while this smaller one existswhile (!deque.isEmpty() && nums[deque.peekLast()] >= nums[i])
deque.pollLast();
deque.offerLast(i);
// first window completes at index k-1if (i >= k - 1)
result[i - k + 1] = nums[deque.peekFirst()];
}
return result;
}
publicstaticvoidmain(String[] args) {
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] mins = minSlidingWindow(nums, k);
for (int m : mins) System.out.print(m + " ");
}
}
Output
-1 -3 -3 -3 3 3
Senior Shortcut:
The deque stores indices, not values, because you need to know when elements expire. Comparing values directly on peekFirst() is a bug — you'll miss OOB windows. Always check indices for expiration, and always pop from the back when the incoming value is smaller (for min) or larger (for max). Commit the pattern: while back >= current, pop back.
Key Takeaway
Sliding window min/max is O(n) with a monotonic deque. The deque is always sorted by value, but accesses and removals happen by index. Every element enters once, exits once — the while loop inside the for loop is still O(n) total.
The Only Sliding Window Template You'll Ever Need — Why Switching Between Fixed and Variable Costs You Zero Rework
Most devs memorize a separate template for fixed and variable windows. That's cargo culting. The sliding window technique is one pattern with one job: maintain a contiguous subset of data that satisfies a condition. The only difference is how tight you hold the condition.
Here's the production insight — use a while loop for the shrink phase, not an if. When you use if, you break the invariant on variable windows where the window can shrink by more than one element. I've fixed more bugs from that single line than anything else.
The template does three things: expand the right pointer, update state, then shrink the left pointer until the condition holds. Track the answer either before or after shrinking depending on whether you want the largest valid window or the smallest invalid one. That's it. Memorize this and you stop caring what the problem calls itself.
SlidingWindowTemplate.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — dsa tutorialpublicintslidingWindow(int[] arr, int k) {
int left = 0, answer = 0;
int state = 0;
for (int right = 0; right < arr.length; right++) {
// Expand
state += arr[right];
// Shrink — use while, not ifwhile (state > k) {
state -= arr[left];
left++;
}
// Record answer (valid window state after shrink)
answer = Math.max(answer, right - left + 1);
}
return answer;
}
Output
// For k = 10, arr = {1,2,3,4,5} -> output: 3
Senior Shortcut:
If your window size is fixed, swap the while for an if. It's the same logic, but the interviewers who notice this know you understand what you're doing.
Key Takeaway
One while loop to shrink, one for loop to expand — that's the entire sliding window pattern. Everything else is window dressing.
Common Mistakes That Get Your Sliding Window Solution Torched in Code Review — And How to Fix Them
I've reviewed hundreds of sliding window submissions. The same three mistakes keep showing up.
First: using an if statement to shrink the window in a variable-length problem. When the window can shrink by more than one element, if breaks immediately. The inner while loop isn't negotiable. Second: tracking the answer before shrinking instead of after, or vice versa, without understanding the difference. If you need the smallest window that violates a condition, record before shrink. If you need the largest valid window, record after shrink. Mixing these up wastes hours.
Third: forgetting to update all state during shrink. You collapse the window but leave stale counters, pointers, or sum values. Then you debug for an hour wondering why your condition never holds. Treat shrink like a transaction — update everything when you move left.
Every one of these mistakes is a 10-minute fix in isolation but a career-limiting look in an interview.
MistakesExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// io.thecodeforge — dsa tutorial// WRONG — if instead of whilepublicintwrongExample(String s) {
int left = 0, maxLen = 0;
Set<Character> set = newHashSet<>();
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
// if breaks when multiple removals neededif (set.contains(c)) {
set.remove(s.charAt(left));
left++;
}
set.add(c);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen; // fails on "abba"
}
// CORRECTpublicintcorrectExample(String s) {
int left = 0, maxLen = 0;
Set<Character> set = newHashSet<>();
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
while (set.contains(c)) {
set.remove(s.charAt(left));
left++;
}
set.add(c);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen; // returns 3 for "abba"
}
Output
// wrongExample("abba") -> 2 (incorrect)
// correctExample("abba") -> 3 (correct)
Production Trap:
The if vs while bug is silent — it works on small inputs, fails on edge cases. Always test with astring like "abba" to catch it.
Key Takeaway
If you only remember one rule: shrink with while, not if. Everything else you can derive.
Visual Intuition — Why Your Brain Needs a Window, Not a Frame
Sliding window is harder to grasp in code because you're managing indices instead of visualizing the range. The fix: draw a physical window. Imagine a strip of paper with numbers. Your window is a cardboard frame that slides left to right. For fixed window (size k), the frame is rigid — you move it one step, drop the leftmost number, add the next rightmost. For variable window, the frame is elastic: expand right to include new data, shrink left to drop constraints. This maps directly to two-pointer logic: left and right are the frame edges. Every time you expand right, you add to your window sum or set. Every time you shrink left, you remove. The deque for minimums is just a second mini-window inside tracking the smallest value. Without this visual anchor, developers confuse shrinking with resetting or treat window boundaries as exclusive vs inclusive incorrectly. Draw it once, code it once — the pattern sticks.
SlidingWindowVisual.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — dsa tutorialpublicclassSlidingWindowVisual {
publicintmaxSumFixed(int[] arr, int k) {
int sum = 0, max = 0;
for (int i = 0; i < k; i++) sum += arr[i];
max = sum;
for (int i = k; i < arr.length; i++) {
sum += arr[i] - arr[i - k];
max = Math.max(max, sum);
}
return max;
}
}
Output
For arr = [2,1,5,1,3,2], k=3 → 9
Production Trap:
Don't use ArrayList.remove(0) to simulate left shrink — O(n) per operation kills performance. Use index pointers.
Key Takeaway
Physical window model maps to left/right pointers — expand right, shrink left, never reset.
Network Traffic Monitoring — Real-Time Anomaly Detection with Fixed Window
Network engineers monitor packets per second (PPS) to detect DDoS attacks. A fixed sliding window of size 60 seconds tracks the count of packets. Each second, a new count arrives and the oldest drops out. The window sum updates in O(1): add new, subtract old. If PPS exceeds threshold (e.g., 10,000), raise alert. This is Maximum Sum Subarray of Size K applied to live data — but instead of max, you check thresholds. The challenge: timestamps aren't perfectly aligned. Use a circular buffer of 60 buckets, each holding one second's count. On each tick, overwrite the oldest bucket and recompute total. This avoids garbage collection from queue operations. False positives spike at minute boundaries if you use naive hour reset — sliding window eliminates that boundary leak. Real implementations in NetFlow/IPFIX collectors use this pattern for near-instant threshold alerts without storing all raw packets.
At t=61s, old t=1s data evicted; window always sums last 60s exactly.
Production Trap:
Don't use System.currentTimeMillis() as bucket index — gaps cause totals to drift. Bucket by integer second boundary.
Key Takeaway
Circular buffer with O(1) updates eliminates boundary leaks and GC pauses in high-throughput monitoring.
Essential LeetCode Problems & Solutions
Mastering the sliding window technique requires pattern recognition across classic problems. Start with LeetCode 125 Valid Palindrome — a simple two-pointer verification that checks characters from both ends, skipping non-alphanumerics. LeetCode 11 Container With Most Water uses two pointers shrinking from the edges, always moving the shorter line inward to maximize area without O(n²) brute force. LeetCode 15 3Sum demands sorting plus a fixed outer loop with two-pointer inner search, avoiding duplicates by skipping identical values. LeetCode 42 Trapping Rain Water tracks left and right max heights, moving the pointer with the smaller max to accumulate trapped water per column. LeetCode 567 Permutation in String applies a fixed-size frequency array sliding over s2, shrinking when counts exceed s1's tally, then checking for exact match. These five problems cover fixed window (567), variable window (42, 11), and multi-pointer (15) patterns. Work through them in order: palindrome builds basic pointer discipline, container adds optimization, 3Sum introduces triplet handling, rain water teaches height tracking, and permutation cements frequency sliding. Each solution reuses the same pointer mechanics with different state tracking, proving the technique's versatility across difficulty levels.
ThreeSumSolution.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — dsa tutorialimport java.util.*;
publicclassThreeSumSolution {
publicList<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = newArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // skip duplicatesint lo = i + 1, hi = nums.length - 1, sum = -nums[i];
while (lo < hi) {
if (nums[lo] + nums[hi] == sum) {
res.add(Arrays.asList(nums[i], nums[lo], nums[hi]));
while (lo < hi && nums[lo] == nums[++lo]);
while (lo < hi && nums[hi] == nums[--hi]);
} elseif (nums[lo] + nums[hi] < sum) lo++;
else hi--;
}
}
return res;
}
}
Output
[[-1,-1,2],[-1,0,1]] for nums = [-1,0,1,2,-1,-4]
Production Trap:
Skipping duplicate detection in 3Sum causes duplicate triplets. Always advance pointers past equal values after a match to keep results unique without HashSet overhead.
Key Takeaway
Sorting enables two-pointer search in 3Sum — O(n²) time beats O(n³) brute force while avoiding duplicate triplets via pointer advancement.
Multi-Pointer Pattern (3Sum)
The multi-pointer pattern extends sliding window to three or more pointers, solving problems like 3Sum (LeetCode 15) where a single loop doesn't suffice. The core idea: fix one pointer (usually the leftmost) and apply a two-pointer sliding window on the remainder of the array. Sorting is mandatory — it transforms the unsorted triple search into a monotonic space where moving pointers predictably changes the sum. For 3Sum, fix i from 0 to n-3, then set lo = i+1 and hi = n-1. The window slides inward based on the comparison of nums[i] + nums[lo] + nums[hi] to zero. When the sum matches, record the triplet and advance both lo and hi past duplicates. When too low, increment lo to increase sum; too high, decrement hi. This pattern generalizes: 3Sum Closest (LeetCode 16) tracks the best delta without exact match, 4Sum (LeetCode 18) nests an additional fixed pointer, and Two Sum II (LeetCode 167) uses only two pointers on sorted array. The key insight is that each additional pointer reduces the problem dimension by one — you trade O(n^k) brute force for O(n^(k-1)) with pointers. Implement this pattern whenever you need combinations from a sorted dataset with a monotonic property.
ThreeSumClosest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — dsa tutorialimport java.util.*;
publicclassThreeSumClosest {
publicintthreeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int closest = nums[0] + nums[1] + nums[2];
for (int i = 0; i < nums.length - 2; i++) {
int lo = i + 1, hi = nums.length - 1;
while (lo < hi) {
int sum = nums[i] + nums[lo] + nums[hi];
if (Math.abs(sum - target) < Math.abs(closest - target))
closest = sum;
if (sum < target) lo++;
elseif (sum > target) hi--;
elsereturn target;
}
}
return closest;
}
}
Output
2 for nums = [-1,2,1,-4], target = 1
Production Trap:
Multi-pointer only works after sorting. Applying it to unsorted data breaks the monotonic guarantee — you'll miss valid combinations or get wrong results. Always sort first.
Key Takeaway
Fix N-2 pointers, then slide two pointers inward — this reduces complexity from O(n^k) to O(n^(k-1)) with guaranteed correctness on sorted data.
Performance Optimization Tips
Optimizing sliding window algorithms focuses on reducing constant factors and eliminating redundant work. Early termination in two pointers: after sorting, if the smallest possible sum (leftmost unprocessed values) already exceeds the target, break the outer loop entirely — no further combinations can work. Similarly, if the largest possible sum is below target, continue the loop but skip inner pointer work. This prunes large portions of the search space in problems like 3Sum Closest or Two Sum II. Avoiding redundant calculations means caching repeated arithmetic: when computing running sums, update incrementally (add new element, subtract evicted element) instead of recalculating from scratch. For maximum window problems like Container With Most Water, compute area once per iteration, not per subarray — track max left/right heights with single variables, not arrays. In frequency-based sliding (Permutation in String), use an integer array of size 26 for character counts rather than HashMap — array access is O(1) with negligible overhead. For monotonic deque patterns (Sliding Window Maximum), insert and remove from deque ends in O(1) amortized, avoiding O(k) window scans. Apply greedy pointer movement: in trapping rain water, always advance the pointer with the smaller boundary height — this minimizes unnecessary comparisons and eliminates extra memory for tracking both maxes simultaneously. These micro-optimizations compound across large inputs, transforming O(n) into O(n) with 3x-10x real-world speedup.
ContainerMostWater.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — dsa tutorialpublicclassContainerMostWater {
publicintmaxArea(int[] height) {
int lo = 0, hi = height.length - 1, max = 0;
while (lo < hi) {
int w = hi - lo;
int h = Math.min(height[lo], height[hi]);
max = Math.max(max, w * h);
if (height[lo] < height[hi]) lo++; // move shorter line inwardelse hi--;
}
return max;
}
}
Output
49 for height = [1,8,6,2,5,4,8,3,7]
Production Trap:
Don't recompute the full sum or frequency each iteration. Incremental updates (add/subtract one element) keep complexity O(n), while full recalculation sneaks O(n²) back in.
Key Takeaway
Move the shorter pointer inward for Container With Most Water — this greedy choice guarantees exploring all candidates without recalculating impossible configurations.
Payment service latency spiked from 12ms to 4,200ms. Error rate jumped from 0.01% to 23%. The API gateway dashboard showed 1,000 RPM enforcement was active, but the payment service logs showed bursts of 10,000+ requests from individual users. The rate limiter appeared to be working — the counter was resetting correctly — but users were exploiting a boundary condition.
Assumption
The rate limiter counter was not resetting properly. The team spent 3 hours investigating Redis TTL issues and counter synchronization across gateway replicas.
Root cause
The sliding window rate limiter used a fixed-size window with a naive boundary check. It counted requests in the current 60-second window (e.g., 12:00:00 to 12:01:00). But at the window boundary (12:00:59 to 12:01:01), a user could send 1,000 requests in the last second of window A and 1,000 requests in the first second of window B — 2,000 requests in 2 seconds. By timing requests at 60-second boundaries, a user could sustain 2,000 RPM indefinitely. The fix required a sliding window log approach (tracking individual request timestamps) or a weighted sliding window counter (partial count from previous window weighted by overlap percentage).
Fix
1. Replaced the fixed-window counter with a sliding window log: store each request timestamp in a sorted set (Redis ZSET). On each request, remove timestamps older than 60 seconds, count remaining timestamps, reject if >= 1,000.
2. Added a ZREMRANGEBYSCORE to prune old timestamps before counting.
3. Set a TTL on the ZSET key to auto-expire after 120 seconds (2x the window) to prevent unbounded memory growth.
4. Added a metric: rate_limiter_boundary_burst_requests_total to detect future boundary exploitation.
5. Load-tested the new implementation with requests timed at window boundaries to verify the fix.
Key lesson
Fixed-window rate limiters are vulnerable to boundary bursts. A user can double their rate by timing requests at window edges.
Sliding window log (storing individual timestamps) is the correct approach for strict rate limiting. It costs more memory but eliminates boundary exploits.
Always load-test rate limiters with adversarial timing patterns — not just uniform request distribution.
Sliding window counters must track overlap from the previous window to smooth the boundary. A weighted counter (e.g., 30% of previous window count) is a practical compromise.
Rate limiter bugs are silent — the counter looks correct but the effective rate is wrong. Test with edge-case timing, not just aggregate counts.
Production debug guideSymptom-first investigation path for sliding window bugs.6 entries
Symptom · 01
Variable window returns a length larger than the expected maximum.
→
Fix
The left boundary jumped backward. Check that windowStart uses Math.max(windowStart, lastSeen + 1) — not just lastSeen + 1. Without Math.max, a character seen before the current window corrupts the boundary.
Symptom · 02
Fixed window sum is incorrect after several slides.
→
Fix
Check that the remove operation subtracts arr[i-k] (the element leaving the window), not arr[i-1] (the previous element). Off-by-one on the remove index causes cumulative sum drift.
Symptom · 03
Variable window never shrinks — left pointer stays at 0.
→
Fix
The constraint check is missing or incorrect. Verify that the while loop checks the constraint (e.g., len(freq) > k) and removes the left character when violated. Without the shrink logic, the window only grows.
Symptom · 04
Window produces wrong results when input contains repeated characters.
→
Fix
The state tracking (HashMap or frequency map) is not updated correctly. Verify that: (1) the character is added to the map after the shrink check, (2) the frequency is decremented before deletion, and (3) the entry is deleted when frequency reaches 0.
Symptom · 05
Algorithm is O(n²) despite using sliding window.
→
Fix
Check if the inner while loop re-scans elements that the outer for loop already processed. Each element should enter and exit the window at most once. If the left pointer moves backward (due to missing Math.max), elements are re-processed.
Symptom · 06
Fixed window edge case: k > array length returns garbage.
→
Fix
Add a guard: if (arr.length < k) return null or throw. The initial window sum computation (sum of first k elements) fails when k exceeds the array length. Handle this before entering the slide loop.
★ Sliding Window Triage CommandsRapid checks to isolate sliding window bugs in production and interview settings.
How do I know when to use the sliding window technique?
Look for these signals in the problem: 'contiguous subarray or substring', 'maximum/minimum/longest/shortest', and a constraint on the window (size k, at most k distinct elements, sum <= target). If you would naturally write a nested loop, a sliding window is likely the optimization.
Was this helpful?
02
What is the difference between sliding window and two pointers?
Two pointers usually work on a sorted array from both ends (one at left, one at right) and meet in the middle — used for pair sum problems. Sliding window has both pointers moving in the same direction with the left pointer always behind the right. Sliding window is a subset of two pointers where both move in the same direction.
Was this helpful?
03
What is the difference between fixed and variable sliding window?
Fixed window has constant size k — slide by adding the new right element and removing the old left element in O(1). Variable window has a size determined by a constraint — expand right freely, shrink left when the constraint is violated. Both achieve O(n).
Was this helpful?
04
When does the sliding window technique not apply?
Sliding window requires a monotone constraint: adding elements makes the window 'more invalid', removing makes it 'more valid'. If the constraint is non-monotone (e.g., exactly k distinct characters, not at most k), you need a different approach such as the 'at most k' minus 'at most k-1' trick.
Was this helpful?
05
How do I know if a problem is a sliding window problem?
Look for: subarray/substring of size k, longest/shortest subarray satisfying a condition, max/min in consecutive windows. If the problem asks about a contiguous range of elements and the answer changes incrementally as you add/remove one element, sliding window applies.
Was this helpful?
06
What is the difference between fixed-size and variable-size sliding windows?
Fixed-size: window length is always k; you add the new right element and remove the leftmost element each step. Variable-size: you expand right greedily and only shrink left when a constraint (e.g., too many distinct chars) is violated. Variable windows track conditions like a hash map of character counts.
Was this helpful?
07
Why is the sliding window O(n) even with a nested while loop?
The left pointer only moves forward, never backward. Each element is visited at most twice: once when the right pointer passes it, and once when the left pointer passes it. Total operations: at most 2n, which is O(n). The nested while loop does not create O(n²) because the left pointer's total movement across all iterations is bounded by n.
Was this helpful?
08
How do I solve 'exactly k distinct characters' with sliding window?
Use the atMost(k) - atMost(k-1) trick. Compute the number of substrings with at most k distinct characters, then subtract the number with at most k-1. Both sub-problems use monotone constraints and work with sliding window. The result isolates exactly k distinct.