The point of two pointers is to use the ordered nature of indices to cut out unnecessary repeated comparisons — turning an exhaustive scan of every combination into a single one-directional pass, discarding combinations that can’t possibly be the answer, and bringing a higher time complexity down to $O(n)$.
The sliding window technique, on the other hand, takes advantage of the fact that when an interval shifts to the right1, only the newly-added element and the one that just left change — everything else in between stays exactly the same. That means there’s no need to rescan the whole structure every time, bringing $O(n \times k)$ or even $O(n^{2})$ down to $O(n)$. The most common use case for sliding window is checking some condition over a contiguous interval.
For example: given an arbitrary integer array and a k, find the maximum sum of any k consecutive elements. Say we’re given this array and k:
1 | nums = [2, 1, 5, 1, 3, 2], k = 3 |
One way to write this:
1 | def findMaxThree(nums: list[int], k: int) -> int: |
But in the code above, consecutive intervals end up overlapping:

- The first interval
[2, 1, 5]and the second[1, 5, 1]overlap on[1, 5] - The second interval
[1, 5, 1]and the third[5, 1, 3]overlap on[5, 1]
And so on. Since we have to visit every element in the array, and at each one we additionally walk k steps to sum up the window, the time complexity is $O(n \times k)$.
The whole point of sliding window is bringing that time complexity down to $O(n)$!
Fixed-size Window
Given an array or string, set up an interval with a fixed length, and keep sliding it to the right, updating the value inside it on the fly — this is called a fixed window.
As mentioned, two adjacent windows share $k - 1$ overlapping elements, and those overlapping elements get reprocessed in both calculations — completely wasted work. Since the sum of those $k - 1$ shared elements never changes, the only real difference between the two windows is the element that just left and the element that just entered.
In other words, once you know the previous window’s sum, the new window’s sum can be computed directly as the previous sum, minus the element that left, plus the element that entered — no need to re-sum the window’s contents at all. That’s exactly why fixed-size windows bring the per-step cost down from $O(k)$ to $O(1)$: the outer loop still has to pass through $n$ positions, but each one only does constant work, so overall it’s $O(n)$.
1 | Algorithm 1 Fixed-size Sliding Window |
To keep the explanation simple, Fig 2 uses 1-indexed arrays:

Variable-size Window
One of the assumptions behind fixed-size windows is that the window size k is a given constant. But flip that around: given an array, find the length of the longest/shortest contiguous subarray satisfying some condition. In that case, there’s no way to know the window size in advance — the window size is the answer we’re looking for — so the window needs to be able to grow and shrink dynamically.
Similar to fixed-size windows, moving to the next interval still means kicking out an existing element and bringing in a new one. The difference, as mentioned, is that the window size itself needs to change based on the condition.
As for how it changes, picture the two ends of the window as movable handles: the right handle grows the window by pulling in a new element that hasn’t been included yet; the left handle shrinks the window by kicking out an element that’s no longer needed.
Both handles only ever move in the same direction (right) — never backward. The only thing that changes is which handle gets pulled, and when: the right handle keeps expanding until the window’s contents satisfy the condition; once it does, the left handle takes over and shrinks the window, checking and updating the answer at each step, until the window no longer satisfies the condition; then the right handle takes over again and keeps expanding. This repeats until the right handle reaches the end of the array.
1 | Algorithm 2 Variable-size Sliding Window |
As shown in the animation below: the right handle expands the window first, until it satisfies the condition; then the left handle takes over and shrinks it, until the window no longer satisfies the condition; this repeats until the right handle reaches the end of the array.

Solutions
LeetCode 643: Maximum Average Subarray I
You’re given an integer array nums of length n, along with an integer k. Find a contiguous subarray of length exactly k whose average value is maximum, and return that maximum average.
Example 1
1 | Input: nums = [1,12,-5,-6,50,3], k = 4 |
Example 2
1 | Input: nums = [5], k = 1 |
This is essentially the same as the example at the very top of this post, just asking for an average instead of a sum. But since k stays fixed, whichever window has the largest sum also has the largest average — find the maximum sum first, then divide by k at the end.
The brute-force solution, matching the style shown earlier in this post:
1 | class Solution: |
Same overlap, same $O(n \times k)$ — optimize it with a fixed-size window instead:
1 | class Solution: |
LeetCode 209: Minimum Size Subarray Sum
You’re given an array nums made up of positive integers, along with a positive integer target. Find the shortest contiguous subarray whose sum is greater than or equal to target, and return its length; if no such subarray exists, return 0.
Example 1
1 | Input: target = 7, nums = [2,3,1,2,4,3] |
Example 2
1 | Input: target = 4, nums = [1,4,4] |
Example 3
1 | Input: target = 11, nums = [1,1,1,1,1,1,1,1] |
The window size here isn’t given — it’s the answer we’re looking for, so this is a variable-size window problem. The brute-force approach expands to the right from each starting point until the sum reaches target:
1 | class Solution: |
The problem is that every starting point re-scans to the right from scratch, giving $O(n^{2})$. Switch to the two handles instead — right only ever expands, left only ever shrinks, each moving at most n steps total:
1 | class Solution: |
LeetCode 1343: Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold
You’re given an integer array arr, along with two integers k and threshold. Return the number of subarrays of length k whose average is greater than or equal to threshold.
Example 1
1 | Input: arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 |
Example 2
1 | Input: arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 |
This is really just a variant of LeetCode 643 — you just need to set up a counter count at the start, checking whether the very first window’s average already meets the threshold and initializing count to 1 or 0 accordingly. That covers the first window (indices 0 through k - 1), which the loop below never checks on its own:
1 | class Solution: |
LeetCode 1004: Max Consecutive Ones III
You’re given an array nums containing only 0s and 1s, along with an integer k. You may flip at most k zeros to ones — return the length of the longest run of consecutive 1s you can get after flipping.
Example 1
1 | Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2 |
Example 2
1 | Input: nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3 |
This problem is really asking for the longest contiguous subarray — even though it’s phrased as the longest run of 1s, you can flip the framing around: use a variable-size window and just track how many 0s are inside it:
- Every step, the right pointer unconditionally expands the window; if the newly-added element is
0, increment the count of zeros in the window - Then check whether that zero count exceeds
k(the maximum number of flips allowed); if it does, shrink the left pointer, kicking out the leftmost element, until the count no longer exceedsk
1 | class Solution: |
1. This interval is exactly the contiguous range bounded by two same-direction left/right pointers — different from the opposite-direction behavior left/right pointers usually have in two pointers. ↩