Fixed Window Rate Limiter Bug — 2x Burst at Boundaries
A fixed-window rate limiter at 1,000 RPM allowed 10,000+ requests per user by timing at boundaries.
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- 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.
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.
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.
- 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.
- 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.
- 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.
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.
- 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).
- 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.
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.
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.
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.
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.
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.
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.
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.
System.currentTimeMillis() as bucket index — gaps cause totals to drift. Bucket by integer second boundary.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.
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.
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.
Rate Limiter Bypassed: Sliding Window Counter Bug Allowed 10x Traffic Spike
- 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.
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 windowSum| File | Command / Code | Purpose |
|---|---|---|
| io | public class MaxSumSubarrayFixedWindow { | Worked Example |
| io | /** | How Sliding Window Works |
| io | public class LongestUniqueSubstring { | Variable Window |
| io | public class LongestSubstringKDistinct { | Variable Window |
| io | public class SlidingWindowMaximum { | Sliding Window Minimum |
| SlidingWindowDetector.java | public class SlidingWindowDetector { | How to Identify Sliding Window Problems |
| SlidingWindowTypes.java | public class SlidingWindowTypes { | Types of Sliding Window |
| SlidingWindowMinDeque.java | public class SlidingWindowMinDeque { | Sliding Window Minimum |
| SlidingWindowTemplate.java | public int slidingWindow(int[] arr, int k) { | The Only Sliding Window Template You'll Ever Need |
| MistakesExample.java | public int wrongExample(String s) { | Common Mistakes That Get Your Sliding Window Solution Torched in Code Review |
| SlidingWindowVisual.java | public class SlidingWindowVisual { | Visual Intuition |
| TrafficMonitor.java | public class TrafficMonitor { | Network Traffic Monitoring |
| ThreeSumSolution.java | public class ThreeSumSolution { | Essential LeetCode Problems & Solutions |
| ThreeSumClosest.java | public class ThreeSumClosest { | Multi-Pointer Pattern (3Sum) |
| ContainerMostWater.java | public class ContainerMostWater { | Performance Optimization Tips |
Key takeaways
Practice These on LeetCode
Interview Questions on This Topic
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
That's Arrays & Strings. Mark it forged?
10 min read · try the examples if you haven't