Sliding Window Technique — Fixed Window, Variable Window, Patterns & Production Pitfalls
- Sliding window converts O(n*k) brute force to O(n) by incrementally updating the window. Each element enters and exits at most once.
- Fixed window: add right element, remove left element at each step. The speedup factor is k.
- Variable window: expand right freely, shrink left when the constraint is violated. Math.max on the left boundary is mandatory.
- Fixed window: constant size k. Add right, subtract left. O(n) total.
- 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.
Window sum drifts after multiple slides.
Print window state: System.out.println("i=" + i + " add=" + arr[i] + " remove=" + arr[i-k] + " sum=" + windowSum)Compare expected sum (Arrays.stream(arr, i-k+1, i+1).sum()) vs windowSumVariable window returns length > input length.
Print left and right on each iteration: System.out.println("left=" + left + " right=" + right + " len=" + (right-left+1))Check if left ever decreases — if yes, Math.max is missingWindow never shrinks — constraint never violated.
Print constraint state on each iteration: System.out.println("distinct=" + freq.size() + " limit=" + k)Verify the while loop condition: while (freq.size() > k)Algorithm is O(n²) despite sliding window pattern.
Add counters: leftMoves=0, rightMoves=0. Increment in each pointer advance. Print at end.If leftMoves + rightMoves > 2*n, the left pointer is re-processing elements.Production Incident
Production Debug GuideSymptom-first investigation path for sliding window bugs.
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.
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].
- Initialize: window_sum = sum(arr[0..2]) = 2+1+5 = 8. max_sum = 8.
- Slide right: i=3. Remove arr[0]=2, add arr[3]=1. window_sum = 8 - 2 + 1 = 7. max_sum stays 8.
- i=4: remove arr[1]=1, add arr[4]=3. window_sum = 7 - 1 + 3 = 9. max_sum = 9.
- i=5: remove arr[2]=5, add arr[5]=2. window_sum = 9 - 5 + 2 = 6. max_sum stays 9.
- Result: 9 (subarray [5,1,3]).
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; public class MaxSumSubarrayFixedWindow { /** * Finds the maximum sum of any contiguous subarray of size k. * Fixed-size sliding window — O(n) time, O(1) space. */ public static int maxSumSubarray(int[] arr, int k) { if (arr.length < k) { throw new IllegalArgumentException("Array length must be >= k"); } // Compute sum of first window int windowSum = 0; for (int i = 0; i < k; i++) { windowSum += arr[i]; } int maxSum = windowSum; // Slide the window: add right element, remove left element for (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; } public static void main(String[] args) { int[] arr = {2, 1, 5, 1, 3, 2}; System.out.println("Max sum (k=3): " + maxSumSubarray(arr, 3)); // 9 — [5,1,3] System.out.println("Max sum (k=2): " + maxSumSubarray(arr, 2)); // 6 — [5,1] System.out.println("Max sum (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) } }
Max sum (k=2): 6
Max sum (k=4): 10
- 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.
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.
Worked example — fixed window k=3, arr=[2,1,5,1,3,2], find max sum: Window [2,1,5]: sum=8, max=8. Slide: -2+1=7. Window [1,5,1]: sum=7. max=8. Slide: -1+3=9. Window [5,1,3]: sum=9. max=9. Slide: -5+2=6. Window [1,3,2]: sum=6. max=9. Answer: 9.
package io.thecodeforge.algo; /** * Summary of the two sliding window flavors. * Both achieve O(n) by visiting each element at most twice. */ public class SlidingWindowPatternSummary { /* * FIXED-SIZE WINDOW * ───────────────── * 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-SIZE WINDOW * ──────────────────── * 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. */ public static void main(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."); } }
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.
- 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.
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; public class MaxSumSubarrayFixedWindow { public static int maxSumSubarray(int[] arr, int k) { if (arr.length < k) { throw new IllegalArgumentException("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; } public static void main(String[] args) { int[] arr = {2, 1, 5 System.out.println(maxSumSubarray(arr, 3)); // 9 System.out.println(maxSumSubarray(arr, 2)); // 6 } }
6
- 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.
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; public class LongestUniqueSubstring { /** * 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. */ public static String longestNonRepeating(String text) { Map<Character, Integer> lastSeenAtIndex = new HashMap<>(); 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 it if (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); } private static char charAt(int index, String text) { return text.charAt(index); } public static void main(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()); } } }
Input: "bbbbb" → "b" (length 1)
Input: "pwwkew" → "wke" (length 3)
Input: "thecodeforge" → "thecod" (length log messages per second6)
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; public class LongestSubstringKDistinct { /** * Finds the longest substring with at most k distinct characters. * Variable-size sliding window with frequency map. * O(n) time, O(k) space. */ public static int longestSubstringKDistinct(String s, int k) { if (k == 0) return 0; Map<Character, Integer> freq = new HashMap<>(); 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 violated while (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. */ public static int exactlyKDistinct(String s, int k) { return longestSubstringKDistinct(s, k) - longestSubstringKDistinct(s, k - 1); } public static void main(String[] args) { System.out.println("At most 2 distinct in 'eceba': " + longestSubstringKDistinct("eceba", 2)); // 3 System.out.println("At most 1 distinct in 'aa': " + longestSubstringKDistinct("aa", 1)); // 2 System.out.println("At most 3 distinct in 'aabacbebebe': " + longestSubstringKDistinct("aabacbebebe", 3)); // 7 System.out.println("Exactly 2 distinct in 'eceba': " + exactlyKDistinct("eceba", 2)); // 3 System.out.println("Exactly 3 distinct in 'eceba': " + exactlyKDistinct("eceba", 3)); // 4 } }
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
- atMost(k): sliding window, O(n). Shrink when distinct > k.
- exactly(k) = atMost(k) - atMost(k-1). Two O(n) passes = O(n) total.
- 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).
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; public class SlidingWindowMaximum { /** * 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. */ public static int[] maxSlidingWindow(int[] arr, int k) { if (arr.length < k || k <= 0) return new int[0]; int[] result = new int[arr.length - k + 1]; Deque<Integer> deque = new ArrayDeque<>(); // stores indices, values in decreasing order for (int i = 0; i < arr.length; i++) { // Remove indices outside the current window while (!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 window while (!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; } public static void main(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] } }
Max in each window of size 3: [3, 3, 5, 5, 6, 7]
- 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.
| Aspect | Fixed-Size Window | Variable-Size Window | Monotonic Deque Window |
|---|---|---|---|
| Window size | Constant k | Determined by constraint | Constant k |
| Pointer movement | Right advances, left follows at distance k | Right advances, left advances when constraint violated | Right advances, left follows at distance k |
| State tracking | Running sum or aggregate | HashMap / frequency map | Deque of indices in sorted order |
| Time complexity | O(n) | O(n) | O(n) amortized |
| Space complexity | O(1) | O(k) — constraint size | O(k) — window size |
| Best for | Max/min/avg sum of size k | Longest/shortest substring with constraint | Max/min in every window of size k |
| Constraint type | None — size is the constraint | Monotone (at most k, sum <= target) | None — size is the constraint |
| Classic problems | Max sum subarray of size k, moving average | Longest substring without repeating, min window substring | Sliding window maximum, sliding window median |
| Key edge case | k > array length | Math.max on left boundary | Deque front outside window |
🎯 Key Takeaways
- Sliding window converts O(n*k) brute force to O(n) by incrementally updating the window. Each element enters and exits at most once.
- Fixed window: add right element, remove left element at each step. The speedup factor is k.
- Variable window: expand right freely, shrink left when the constraint is violated. Math.max on the left boundary is mandatory.
- Use a HashMap or frequency counter to track window state for string problems. Delete entries when count reaches 0.
- Recognise the pattern: any 'contiguous subarray/substring' problem with an optimization goal is a sliding window candidate.
- Sliding window requires a monotone constraint. Non-monotone constraints (exactly k) need the atMost(k) - atMost(k-1) trick.
- Monotonic deque maintains the window's max/min in O(1) amortized. Use it for 'max of every window of size k'.
- Always test variable windows with repeated elements ('abba', 'tmmzuxt') to catch the Math.max bug.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the time complexity of the sliding window technique and why? Prove that each element is visited at most twice.
- QGiven an array and integer k, find the maximum sum of any contiguous subarray of size k. What is the brute force complexity and how does sliding window improve it?
- QFind the length of the longest substring without repeating characters. What edge case does Math.max on the left boundary handle?
- QHow would you find the longest substring with exactly k distinct characters? Why can't you use sliding window directly, and what trick do you use?
- QExplain the monotonic deque pattern for sliding window maximum. Why is it O(n) amortized?
- QGiven an array of integers and a target sum, find the length of the shortest subarray with sum >= target. How does the variable window pattern apply here?
- QHow would you implement a sliding window rate limiter that enforces exactly N requests per minute without boundary burst exploits?
- QWhat is the difference between sliding window and two pointers? When would you choose one over the other?
- QGiven a string s and a string t, find the minimum window in s that contains all characters of t. What state do you maintain and how do you know when the window is valid?
- QHow would you find the median of every window of size k in O(n log k) time? What data structures would you use?
Frequently Asked Questions
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.
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.
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).
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.
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.
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.
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.
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.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.