You've read the pattern guides. You've solved 20 LeetCode problems. You know sliding window.
Then you're in an interview, and your solution fails on test case 47. Or worse—it passes locally but times out on submission. You stare at your code, muttering "but I used two pointers..."
Here's the truth: knowing the pattern is only half the battle. The other half is avoiding the landmines that turn a conceptually correct solution into a buggy mess.
In this guide, I'll walk you through the 6 most common sliding window mistakes I've seen in code reviews and interviews, complete with:
- Real examples of broken code
- Why the mistake happens
- How to fix it (and how templates prevent it)
- A debugging checklist you can memorize
By the end, you'll code defensively and catch bugs before they cost you the interview.
TL;DR — 6 Deadly Sliding Window Mistakes
- Mis-identifying the pattern: Using sliding window for sum-equals-K with negatives (use prefix sums instead)
- Off-by-one errors: Calculating window size as
right - leftinstead ofright - left + 1 - Missing shrink logic: Forgetting the
whileloop to shrink until valid - Ignoring edge cases: Empty input, single element, zeros, negative numbers
- Poor invariant definition: Shrinking at the wrong time or using wrong condition
- Performance traps: Nested loops making it instead of
Fix: Use the standard template to avoid 90% of these bugs.
Mis-identifying the Problem Pattern
This is the meta-mistake: using sliding window when you shouldn't.
Using Sliding Window When Prefix Sum/DP is Needed
The Scenario: You see "subarray sum" and immediately think "sliding window!"
But then you notice: the array has negative numbers, or the problem asks for "number of subarrays with sum exactly K."
Why It Fails: Sliding window requires monotonicity. Negative numbers break this. When you expand the window and the sum decreases, you can't make a greedy decision about whether to shrink or expand.
Example Error:
# WRONG: Trying to find subarrays with sum = k using sliding window
def subarraySum(nums: List[int], k: int) -> int:
left = 0
current_sum = 0
count = 0
for right in range(len(nums)):
current_sum += nums[right]
# This logic is broken with negative numbers
while current_sum > k and left <= right:
current_sum -= nums[left]
left += 1
if current_sum == k:
count += 1
return count
# Test case that breaks it:
nums = [1, -1, 1, -1, 1]
k = 0
# Expected: 4 (indices [0,1], [1,2], [2,3], [3,4])
# Your code returns: 1 (only finds [0,1])Why This Happens: After adding nums[right] = -1, the sum decreases even though we expanded. The while loop doesn't know whether to keep shrinking or wait for more elements.
The Fix: Use Prefix Sum + Hash Map
For "subarray sum equals K" with any numbers (including negatives), use this pattern:
def subarraySum(nums: List[int], k: int) -> int:
prefix_sum = 0
count = 0
sum_count = {0: 1} # Handle subarrays starting at index 0
for num in nums:
prefix_sum += num
# If (prefix_sum - k) exists, we found a subarray
if prefix_sum - k in sum_count:
count += sum_count[prefix_sum - k]
sum_count[prefix_sum] = sum_count.get(prefix_sum, 0) + 1
return countDecision Rule: If the problem involves:
- Exact sum with unrestricted values → Prefix sum
- "At most/at least" constraint with non-negative values → Sliding window
- Non-contiguous elements (subsequences) → DP
Pattern Recognition Tip: Check the constraints. If
- 10 ^ 4 <= nums[i] <= 10 ^ 4, that's a red flag for sliding window on sums. Learn more about when NOT to use sliding window and why negative numbers break monotonicity.
Shrinking/Expanding Logic Mistakes
Even when sliding window is the right choice, implementation bugs are common.
Off-by-One: The Window Size Error
The Mistake: Calculating window size as right - left instead of right - left + 1.
Example:
// WRONG
function maxSubArray(nums, k) {
let left = 0, maxLen = 0;
for (let right = 0; right < nums.length; right++) {
// ... window logic ...
maxLen = Math.max(maxLen, right - left); // BUG!
}
return maxLen;
}
// When left = 0, right = 2, window contains indices [0,1,2] = 3 elements
// But right - left = 2 - 0 = 2 ❌
// Correct: right - left + 1 = 2 - 0 + 1 = 3 ✓Why This Happens: Confusion between "index distance" and "element count." Array indices are 0-based, but element count starts at 1.
The Fix: Always use right - left + 1. Memorize it as part of your sliding window template. This is also covered in our guide on off-by-one errors.
Missing Shrink Condition: The Infinite Loop
The Mistake: Not shrinking when the window becomes invalid.
Example:
# WRONG: Finding longest substring with at most k distinct characters
def lengthOfLongestSubstringKDistinct(s: str, k: int) -> int:
left = 0
char_count = {}
max_len = 0
for right in range(len(s)):
char_count[s[right]] = char_count.get(s[right], 0) + 1
# MISSING: while len(char_count) > k: ...
max_len = max(max_len, right - left + 1)
return max_len
# This always returns len(s), ignoring k entirely!The Fix: Always include the shrink loop for variable-size windows:
# CORRECT
def lengthOfLongestSubstringKDistinct(s: str, k: int) -> int:
left = 0
char_count = {}
max_len = 0
for right in range(len(s)):
char_count[s[right]] = char_count.get(s[right], 0) + 1
# Shrink until valid
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
max_len = max(max_len, right - left + 1)
return max_lenCode Snippet: Wrong vs Correct Shrink
| Wrong Pattern | Correct Pattern |
|---|---|
if window_invalid(): | while window_invalid(): |
left += 1 (without removing state) | Remove state, then left += 1 |
| Checking validity before expanding | Expand, then shrink if invalid |
Key Insight: The shrink loop is while, not if. You may need to shrink multiple times to restore validity.
Ignoring Edge Cases
Edge cases are where most "correct" solutions fail in interviews.
Negative Numbers, Zeros, Empty Input
Common Edge Cases:
- Empty input:
s = ""ornums = [] - Single element:
s = "a" - All identical elements:
nums = [5, 5, 5, 5] - Zeros:
nums = [0, 0, 1, 2](for product problems, this breaks naive multiplication) - Negative numbers: Already covered for sums; also breaks product problems
Example Failure with Zeros:
// WRONG: Maximum product subarray
public int maxProduct(int[] nums) {
int result = nums[0];
int currentProduct = 1;
for (int num : nums) {
currentProduct *= num; // If num = 0, product becomes 0 forever!
result = Math.max(result, currentProduct);
}
return result;
}
// Input: [2, 3, -2, 4]
// Expected: 6 (subarray [2,3])
// Your code: 0 (because -2 * 4 makes it negative, then stays negative)The Fix: Reset state when encountering zeros or negative numbers:
public int maxProduct(int[] nums) {
int maxSoFar = nums[0];
int maxEndingHere = 1;
int minEndingHere = 1;
for (int num : nums) {
if (num < 0) {
// Swap max and min because multiplying by negative flips sign
int temp = maxEndingHere;
maxEndingHere = minEndingHere;
minEndingHere = temp;
}
maxEndingHere = Math.max(num, maxEndingHere * num);
minEndingHere = Math.min(num, minEndingHere * num);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}Edge Case Checklist:
- ✅ Test with empty input
- ✅ Test with single element
- ✅ Test with all elements the same
- ✅ Test with zeros (for product/division)
- ✅ Test with extreme values (INT_MAX, INT_MIN)
Poor Invariant Definition
An "invariant" is a condition that's always true at a specific point in your loop. Poor invariants lead to subtle bugs.
Defining the Window Validity Incorrectly
The Problem: You define "valid window" in a way that doesn't match the problem constraints.
Example: "Find the longest substring with at most 2 distinct characters."
WRONG invariant: "The window has exactly 2 distinct characters."
RIGHT invariant: "The window has at most 2 distinct characters."
Why It Matters: With the wrong invariant, you'll shrink too early:
# WRONG: Shrinks even when valid
while len(char_count) != 2: # Should be > 2, not != 2
# ...This incorrectly shrinks windows with 1 distinct character, even though they're valid.
How to Fix: Write Down Your Invariant
Before coding, write a comment:
def lengthOfLongestSubstringTwoDistinct(s: str) -> int:
# INVARIANT: After the while loop, the window [left, right]
# contains at most 2 distinct characters
left = 0
char_count = {}
max_len = 0
for right in range(len(s)):
char_count[s[right]] = char_count.get(s[right], 0) + 1
# Restore invariant
while len(char_count) > 2:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
# Now invariant holds: window is valid
max_len = max(max_len, right - left + 1)
return max_lenDebugging Tip: If your code is failing mysteriously, print the window state and check if your invariant actually holds after the shrink loop.
Performance/Memory Traps
Even with correct logic, you can still fail on time/space limits.
Using Nested Loops: The O(N^2) Trap
The Mistake: Adding an inner loop that scans the entire window.
Example:
// WRONG: O(N^2) instead of O(N)
public int lengthOfLongestSubstring(String s) {
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
// Check if s[right] is duplicate
for (int i = left; i < right; i++) { // BUG: O(N) scan!
if (s.charAt(i) == s.charAt(right)) {
left = i + 1;
break;
}
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}Why This Fails: The inner loop makes it . For , this times out.
The Fix: Use a Set or Map for lookups:
public int lengthOfLongestSubstring(String s) {
Set<Character> seen = new HashSet<>();
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
while (seen.contains(s.charAt(right))) {
seen.remove(s.charAt(left));
left++;
}
seen.add(s.charAt(right));
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}Forgetting to Update Window State
The Mistake: Adding elements to the window but not removing them when shrinking.
Example:
// WRONG: Memory leak—map grows without bound
function lengthOfLongestSubstring(s) {
let left = 0, maxLen = 0;
const charIndex = new Map();
for (let right = 0; right < s.length; right++) {
charIndex.set(s[right], right);
while (/* some condition */) {
left++;
// BUG: Not removing s[left-1] from charIndex!
}
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}The Fix: Always mirror your state updates:
- When you add to the window → update state
- When you shrink from the window → update state
// CORRECT
function lengthOfLongestSubstring(s) {
let left = 0, maxLen = 0;
const charSet = new Set();
for (let right = 0; right < s.length; right++) {
while (charSet.has(s[right])) {
charSet.delete(s[left]); // Remove from state
left++;
}
charSet.add(s[right]); // Add to state
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}Takeaways & Checklist
Pre-Coding Checklist
Before writing any sliding window solution:
Is this really sliding window?
- ✅ Contiguous elements (subarray/substring)
- ✅ Monotonic constraint (not sum-equals-K with negatives)
- ✅ Efficient state updates possible
Fixed or variable size?
- Fixed → Simple slide (add one, remove one)
- Variable → Expand in
for, shrink inwhile
What's my invariant?
- Write it down: "After shrinking, the window is [definition of valid]"
What state do I track?
- Sum →
int - Unique elements →
Set - Frequency →
Map - Max/min → Monotonic deque
- Sum →
Debugging Checklist
If your solution fails:
- Check window size calculation:
right - left + 1, notright - left - Verify shrink loop exists:
while, notif - Print window state: Log
[left, right]and your data structure - Test edge cases: Empty, single element, all same
- Check time complexity: Are you scanning the window inside the loop? ( bug)
- Verify state mirroring: Every
addwhen expanding should have aremovewhen shrinking
When to Use Templates
If you're making these mistakes repeatedly, stop freestyling and use a template. Templates enforce:
- Correct loop structure
- Invariant maintenance
- State update symmetry
FAQ
Q: How do I debug a sliding window solution that's failing on a specific test case?
A: Add print statements inside your loop to track left, right, window contents, and your validity condition. The bug is usually: (1) wrong shrink condition, (2) missing state update, or (3) off-by-one in size calculation.
Q: My solution works for most cases but fails on large inputs. What's wrong?
A: Likely a time complexity issue. Check if you have an scan inside your main loop (making it ). Use a Set or Map for lookups.
Q: I keep getting "index out of bounds" errors. Why?
A: You're probably accessing arr[right] or arr[left] outside the loop bounds. Make sure:
rightnever exceedslen(arr) - 1(usefor right in range(len(arr)))leftdoesn't exceedright(addwhile left <= rightguard)
Q: Should I always use a HashMap for sliding window?
A: No. Use the simplest data structure that works:
- Sum → single
int - Unique elements →
Set - Frequency tracking →
Map - Max/min → Deque (for advanced problems)
Q: What's the difference between if and while for shrinking?
A: Use while. A single shrink might not restore validity. Example: window has 5 distinct chars, k = 2. You need to shrink multiple times, not just once.
Q: How can I avoid these mistakes in a real interview?
A: Use LeetCopilot's Study Mode to practice with instant feedback. It catches these bugs as you code and explains why they're wrong—like having a senior engineer pair programming with you.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
