Sliding Window Bugs That Pass Tests and Fail Interviews

June 6, 202611 min read
dsaalgorithmsinterview-prepleetcode
Sliding Window Bugs That Pass Tests and Fail Interviews
TL;DR
  • Fixed windows slide without shrinking — decide the window type before writing a single line of code
  • Maximum windows update after the shrink loop; minimum windows update inside it — getting this backwards gives the wrong size
  • In LeetCode 424, leave max_freq inflated during shrinking — a stale value keeps the algorithm O(n) and avoids a whole class of recalculation bugs
  • Delete zero-count keys from frequency mapslen(count) relies on it for distinct-element and at-most-K problems
  • Exactly K distinct = atMost(K) minus atMost(K-1) — two independent calls with separate pointers and frequency maps
  • Sliding window fails when validity is non-monotonic — negative numbers and palindrome checks break the pattern
  • Copy the inequality sign word for word from the problem constraint into your shrink condition, not what feels right

You got the pattern. Two pointers, a frequency map, expand right, shrink left when the window goes invalid. You solved LeetCode 3 in twenty minutes. You felt great. Then LeetCode 76 shows up, same template in your head, and the submission fails on test case 47.

The pattern is correct. The implementation is broken. Sliding window bugs are invisible on simple inputs and devastating on hidden test cases. Here are the recurring ones that bite candidates most, with the exact fix for each one.

Fixed Windows Don't Shrink

The first decision with any sliding window problem is whether the window size is fixed or variable. Most candidates know this distinction in theory and then immediately ignore it when they open their editor.

A fixed window slides. It never shrinks.

LeetCode 567 (Permutation in String) and 438 (Find All Anagrams) require a window exactly as long as the pattern string. When you advance right by one, you add the incoming character and remove the outgoing one. No while invalid: left++. The window just moves. That is the whole trick.

The mistake is writing a shrink loop for a fixed-window problem. Shrinking feels correct because it removes invalid characters, but it destroys the fixed-length guarantee the problem depends on. For 567, shrinking below len(s1) means you might slide right past a valid permutation. You will. It will be on test case 38.

# Correct fixed-window slide for LeetCode 567 for i in range(len(s1), len(s2)): w_count[s2[i]] += 1 outgoing = s2[i - len(s1)] w_count[outgoing] -= 1 if w_count[outgoing] == 0: del w_count[outgoing] if w_count == s1_count: return True

Add one, remove one, check. No while. Resist the urge.

For variable windows (LeetCode 3, 76, 424), the window size fluctuates based on validity. For fixed windows, it never does. Identify the type before you write a single line.

Fixed vs variable window: fixed window shifts by 1 every step while variable window grows and shrinks based on validity

When to Update Your Answer

Every sliding window problem asks the same question eventually: where do you record the result? Before the shrink loop? Inside it? After it? Candidates pick one and hope for the best. There is a rule.

For maximum windows, update after the shrink loop. For minimum windows, update inside it.

When you want the longest valid window, the shrink loop restores validity. Only after it finishes do you have the largest valid window ending at the current right pointer. That is when you record the maximum:

for right in range(len(s)): # expand: add s[right] to state while invalid_condition: # shrink: remove s[left] from state left += 1 max_len = max(max_len, right - left + 1) # after shrink

When you want the shortest valid window (LeetCode 76, 209), the window is valid as soon as it satisfies the condition. Each time you shrink and stay valid, you have found a potentially smaller answer. Update inside the loop:

for right in range(len(nums)): current_sum += nums[right] while current_sum >= target: # window is valid here min_len = min(min_len, right - left + 1) # inside shrink current_sum -= nums[left] left += 1

Max problems shrink to restore validity, so update after. Min problems shrink while valid, so update during. Getting this backwards produces a result that is either one window too large or one too small. The tests on simple inputs will probably pass anyway. The hidden test cases will not.

The max_freq Trick Nobody Explains

LeetCode 424 (Longest Repeating Character Replacement) has one non-obvious property that most implementations get wrong: max_freq, the count of the most common character in the current window, should never decrease when the window shrinks.

Most people try to recompute it on shrink. They think this is correct and necessary. It is neither:

# Wrong: expensive and unnecessary if char_count[s[left]] == max_freq: max_freq = max(char_count.values()) left += 1

The correct approach leaves max_freq as-is and shrinks by exactly one:

for right in range(len(s)): char_count[s[right]] += 1 max_freq = max(max_freq, char_count[s[right]]) if (right - left + 1) - max_freq > k: char_count[s[left]] -= 1 left += 1 # shrink by one, not by while loop max_len = max(max_len, right - left + 1)

Why does a stale max_freq work? You are looking for the longest valid window you have ever seen. You only care about windows larger than the current best. When max_freq is inflated because the character that earned it left the window, the shrink condition becomes harder to satisfy, so the window size stays constant rather than growing. The window only grows when a real new max_freq is found.

A stale max_freq cannot make the window grow incorrectly. It just means the window treads water until a better maximum arrives. This keeps the algorithm at O(n) and eliminates a whole class of recalculation bugs.

The same logic explains why you use if, not while, to shrink in this problem. You are never looking for a shorter window once the best grows, so you just advance left by one when the condition fires. If you use while here, you will get wrong answers and have no idea why.

Deleting Zero-Count Keys Is Not Cleanup

When you use a hash map for character frequencies and shrink the window, always delete keys whose count drops to zero. This is not tidiness. It changes correctness.

The bug surfaces in problems that count distinct elements using len(count). If a character leaves the window but its key stays at count 0, len(count) never decreases. Your window thinks it still has that element and shrinks less than it should.

Delete the key when the count reaches zero:

count[arr[left]] -= 1 if count[arr[left]] == 0: del count[arr[left]] # required, not optional left += 1

This matters most in problems like LeetCode 992 and any "at most K distinct" problem. The shrink loop uses while len(count) > k to decide when to move left. If dead keys inflate len(count), that loop fires when it should not, and you count too few valid subarrays. Your solution will look totally reasonable and be completely wrong.

Exactly K Needs a Transformation

"Find subarrays with exactly K distinct integers" breaks direct sliding window implementations. A window with exactly K distinct elements can go invalid in two ways: it gains a new distinct (too many) or loses one (too few). One shrink condition cannot handle both at once. Candidates try to write one anyway. It does not work.

The fix: exactly(K) = atMost(K) minus atMost(K-1).

You write a function that counts subarrays with at most K distinct elements, then call it twice:

def exactly_k(arr, k): return at_most_k(arr, k) - at_most_k(arr, k - 1) def at_most_k(arr, k): count = {} left = 0 result = 0 for right in range(len(arr)): count[arr[right]] = count.get(arr[right], 0) + 1 while len(count) > k: count[arr[left]] -= 1 if count[arr[left]] == 0: del count[arr[left]] left += 1 result += right - left + 1 # all subarrays ending at right return result

Two things to get right. First, result += right - left + 1 counts all subarrays ending at right starting from any index between left and right inclusive. Every one of those satisfies the constraint. Second, each call to at_most_k runs its own independent loop with its own left pointer and its own frequency map. Do not try to share state between the two calls.

LeetCode 992 is the canonical problem. LeetCode 930 (Binary Subarrays With Sum) uses the identical transformation.

When Sliding Window Does Not Apply

Sliding window works only when window validity changes monotonically with size. Adding an element to a valid window should not unpredictably restore validity after breaking it. When it does, you are trying to apply the wrong tool.

If growing a valid window can make it invalid and then valid again, sliding window cannot be used.

The clearest failure case: "Subarray Sum Equals K" when the array contains negative numbers. Your window has sum 7 and target is 10. You expand hoping to reach 10. A -5 followed by a +8 could bring you there, but the window's validity bounces around with every element you add. There is no consistent rule for when to shrink because shrinking does not predictably restore or break validity.

LeetCode 560 (Subarray Sum Equals K) with negatives requires a prefix sum with a hash map, not a sliding window. "Longest Palindromic Substring" fails similarly: adding one character to a non-palindrome window does not follow a monotonic validity pattern.

Before reaching for the template, ask: if this window is currently invalid, can removing elements from the left always make it valid again? If the answer is "not necessarily," you need a different approach. This mistake produces confident-looking code that passes small tests and fails on every negative-number case. Interviewers have seen it before.

The Sign Your Shrink Condition Gets Wrong

One last bug. The simplest: using > when you need >=, or the reverse.

LeetCode 209 asks for the minimum length subarray with sum at least target. The shrink condition is:

while current_sum >= target: # correct

Using > target stops shrinking one step early. The window's sum equals target exactly. The window is valid. You leave it, advance right, and miss a smaller window that the problem wanted you to find. You will not notice. Your examples will pass.

Match the shrink condition to the problem's constraint word for word. "At least K" means shrink while >= K. "Greater than K" means shrink while > K. Read the constraint, then copy the inequality directly into your code. Do not paraphrase it in your head first.

This sounds obvious. It is still the cause of a surprising number of wrong submissions on LeetCode 209. One character, wrong answer, no idea why.

Where All These Sliding Window Bugs Combine

LeetCode 76 (Minimum Window Substring) is where every bug on this list can appear simultaneously: frequency tracking instead of existence checks (because t can have duplicate characters like "AAB"), updating inside the shrink loop, case-sensitive matching, and returning "" rather than None or the full string when no valid window exists.

If you can implement 76 correctly without looking anything up, you have internalized all of the above.

A practice order that adds exactly one layer of complexity per problem: LeetCode 3, then 567, then 424, then 209, then 76, then 992. Do not jump straight to 76. You will think you solved it, submit, get wrong answer on test case 47, and have no idea which of these six bugs you hit.


Recap:

  • Fixed windows slide without shrinking. Variable windows do both. Decide before you code.
  • Max windows update after the shrink loop. Min windows update inside it.
  • In LeetCode 424, leave max_freq inflated during shrinking. This is correct.
  • Delete zero-count keys from frequency maps. len(count) depends on it.
  • Exactly K distinct = atMost(K) minus atMost(K-1). Two independent calls, separate state.
  • Sliding window requires monotonic validity. Negative numbers break this.
  • Copy the inequality sign directly from the problem constraint into your shrink condition.

Recognizing which sliding window variant a problem needs before writing any code is covered in Sliding Window Problems Leave Clues. The foundational technique is in The Sliding Window Algorithm. For the boundary between sliding window and two pointers, Two Pointers vs Sliding Window covers why they look identical on the surface and how to tell them apart.

Knowing these fixes and applying them under interview pressure are two different skills. SpaceComplexity runs voice-based DSA mock interviews with rubric-based feedback, so you find out which bugs survive into timed conditions before your real interview does.

Further Reading