Skip to content

Two Pointers and Sliding Window

Use two positions that move through an array or string to replace slow nested loops with a single pass. Includes fast and slow pointers.

beginner25 minAsked oftenTwo pointersSliding windowFast and slow pointersSubarraySubstring

Last reviewed 24 Oct 2026 · by TheLearningLoop Team

Read first: Data Structures & Algorithms: The Big Picture (blog post)

Progress is saved on this device. Create a free account to keep it everywhere.

On this page ▾

Key takeaways

  • Two pointers walk through the data together, so a problem that needs a nested loop often takes one pass, O(n).
  • A sliding window keeps a running answer for a stretch of items, and updates it by adding one item and removing one.
  • Fast and slow pointers move at different speeds, which finds the middle of a list or a cycle with O(1) memory.

When to use two pointers or a sliding window

Use two pointers when you have:

  • A sorted array and you want a pair or triple with a given sum, or to merge two sorted lists.
  • A question that compares the two ends, such as a palindrome or the container with the most water.
  • A need to change an array in place, for example removing duplicates or moving zeros to the end.
  • A linked list where you want the middle, or to know whether it loops (fast and slow pointers).

Use a sliding window when you have:

  • A question about a contiguous stretch of an array or string: the longest, the shortest, the biggest total, or "at most k different items".
  • A running total or count over the last k items, such as a moving average or the busiest minute.

Use something else when:

  • The array is not sorted and you want a pair: use a hash map.
  • The items you want are not next to each other (a subsequence): use dynamic programming.
  • The window total can go down when you add an item (negative numbers) and you need an exact sum: use prefix sums with a hash map.

There is no library to import. This is a technique, not a data structure. Languages give you helpers (see the real-world section), but the idea is two integers and a loop.

How two pointers and sliding windows work

A pointer here is just an index. Two pointers means keeping two indexes and moving them according to a rule, instead of using a nested loop that tries every pair.

The pointers only move forward (or inwards), never back, so each item is visited a constant number of times and the whole pass is O(n).

There are three common shapes:

  1. Inwards from both ends. Start one pointer at the left and one at the right of a sorted array. If the sum is too small, move the left pointer right. If it is too big, move the right pointer left. Each move rules out a whole group of pairs.
  2. Fast and slow. Both start at the same place and one moves faster, for example two steps for every one. In a linked list, when the fast pointer reaches the end, the slow one is at the middle. If the list loops, the fast pointer eventually catches the slow one.
  3. A window. The left and right pointers are the edges of a stretch of items. The right edge grows the window and the left edge shrinks it. A fixed window always holds k items. A variable window grows and shrinks to keep a rule true, such as "no repeated letters".

The trick that makes a window fast is to update, not recompute. When the window slides one step, add the item that came in, subtract the item that left, and the total is correct. You never add up all k items again.

Try it

Pick one of the three techniques, then press Run and step through. Watch the pointers and the counters.

Two pointers and sliding window

Step 1 of 6 · Press Space to play, arrow keys to step
Examples:

Find two numbers that add up to 10. The array is sorted, so put one pointer at each end.

pairs checked:
0
brute force would check:
15

Things to try:

  • Pair with a given sum: use 1 3 4 6 8 11 with target 10. It finds 4 + 6 after 5 checks, where checking every pair could take up to 15. Then use target 2: it proves no pair exists after 5 checks, where checking every pair would take 15.
  • Best total of k in a row: use 2 1 5 1 3 2 9 1 with k = 3. Each slide adds one number and removes one. The best total is 14, starting at index 4.
  • Longest run with no repeats: use abcabcbb. The window grows to abc, then shrinks from the left whenever a letter repeats. The answer is length 3.

Two pointers and sliding window operations

Inwards pointers (sorted array). Compare the two items. Decide which pointer to move from how the sum compares with the target. Stop when they meet.

Fixed window. Add up the first k items once. Then for each step, add the new item on the right and subtract the old item on the left. Keep the best total seen.

Variable window. Move the right edge one item at a time. If the new item breaks the rule, move the left edge forward until the rule holds again. Record the window length after each step.

Fast and slow pointers. Move the slow pointer one step and the fast pointer two steps. If they ever point at the same item, there is a loop. If the fast pointer reaches the end, there is none.

Interview fact. A while loop inside a for loop does not mean O(n²). If the left pointer only ever moves forward, it moves at most n times across the whole run, so the total work is about 2n, which is O(n).

Two pointers and sliding window time complexity

TechniqueTimeExtra memory
Inwards pointers on sorted dataO(n)O(1)
Fixed sliding windowO(n)O(1)
Variable sliding window (numbers)O(n)O(1)
Variable window with a set of seen itemsO(n)O(k), where k is the number of distinct items
Fast and slow pointers on a linked listO(n)O(1)

If the data is not sorted and you must sort it first for inwards pointers, add O(n log n) for the sort.

Two pointers vs other approaches

The right choice depends on the problem. n is the number of items.

What you needTwo pointers or windowNested loopsHash mapSort, then binary search
Pair with a given sum, array already sortedO(n) time, O(1) memoryO(n²)O(n) time, O(n) memoryO(n log n)
Pair with a given sum, array not sortedO(n log n) (sort first)O(n²)O(n) time, O(n) memoryO(n log n)
Best total of k in a rowO(n)O(n × k)Not usefulNot useful
Longest run with no repeatsO(n)O(n²) or worseO(n), also a windowNot useful
Extra memory for numbersO(1)O(1)O(n)O(1)
Needs sorted inputOnly for the inwards versionNoNoYes

The bold cells show where each wins. Two pointers and windows are the cheapest on memory, but they need either sorted data or a question about a contiguous stretch.

How to explain two pointers and sliding windows in an interview

  • Say what each pointer means: "left is the start of the window, right is the next item to add."
  • Say why the pointers only move forward, and so why it is O(n) even with an inner loop.
  • State what you update when it moves: "add the new item, subtract the old one", not "recompute the window".
  • Say when it does not apply: unsorted pairs (use a hash map), non-contiguous choices (use dynamic programming).
  • Give both costs: O(n) time, O(1) memory, or O(k) if you keep a set.

Where you meet two pointers and sliding windows in real systems

You rarely write these loops for their own sake, but the idea sits inside network stacks, monitoring tools and compressors. Here are three drawn out, the first with code you can run, then more examples.

Example 1: Rate limits and traffic spikes

A rate limiter asks, "how many requests arrived in the last 5 seconds?" A monitoring tool asks the same thing to spot spikes. The requests have timestamps in order, so a window works: the right edge adds each new request and the left edge drops requests that are too old. Open the example below to see the picture and the code.

Code: the most requests in any 5-second windowTime O(n)Memory O(1)
Seconds1238910111230
Requests arrived at seconds 1, 2, 3, 8, 9, 10, 11, 12 and 30. The five requests from second 8 to second 12 fall inside one 5-second window, which is the busiest.
def busiest_window(times, width):
    """Most requests that arrived inside any window of `width` seconds.

    `times` is a sorted list of request times in seconds. A window holds the
    requests whose times differ by less than `width`. This is the idea behind
    spotting traffic spikes and rate limits.

    Two pointers form the window: `right` adds the newest request, and `left`
    moves forward to drop requests that are too old. Neither pointer ever goes back.

    Time:   O(n). `left` and `right` each move forward at most n steps in total.
    Memory: O(1). Only a few integers are stored, whatever the length of `times`.
    """
    best = 0
    left = 0
    for right in range(len(times)):                      # grow the window on the right
        while times[right] - times[left] >= width:       # too wide: drop from the left
            left += 1
        best = max(best, right - left + 1)               # requests currently in the window
    return best

Each version printed 5 for those times and 0 for an empty list. All five were compiled and run, and none of them uses a package. The Go version uses the built-in max, which needs Go 1.21 or newer.

Example 2: TCP flow control

TCP, the protocol behind most web traffic, lets a sender have only a limited amount of data in flight without confirmation. That limit is the window. As the receiver confirms data, the window slides forward and the sender may send more. The "left edge" is the first unconfirmed byte and the "right edge" is the last byte the sender is allowed to send.

Byte numbers12345678910
Bytes 1 to 3 are confirmed and dimmed. Bytes 4 to 8 are the window: sent or allowed to send. When byte 4 is confirmed, the window slides one place right.

Example 3: Moving averages on dashboards

A graph of "average response time over the last 5 minutes" is a sliding window. Recomputing the average from scratch every time would re-read every point. Instead the system adds the newest value to a running total, subtracts the value that just left the window, and divides.

Window 1486102Window 2486102
Values 4, 8 and 6 average to 6. Slide one place: add 10, remove 4, and the average of 8, 6 and 10 is 8. Only two values are touched per step.

More real-world scenarios

4. Palindrome and symmetry checks

To check whether a word or a sequence reads the same both ways, put one pointer at each end and compare while moving inwards. Spell checkers, DNA tools and input validators use the same idea.

5. Merging sorted data

Merging two sorted lists uses one pointer in each list, always taking the smaller item and moving that pointer. This is the merge step of merge sort, and databases use the same step to combine two sorted inputs.

6. Finding two items that fit a budget

"Find two products that cost exactly 50 together" is the pair-sum problem. With the prices sorted, two pointers find the pair in one pass instead of trying every combination.

7. Detecting loops in linked structures

Fast and slow pointers detect a cycle in a linked list using O(1) memory. If the fast pointer ever lands on the slow one, the list loops. The idea appears in tools that check for circular references in chains of links.

8. Longest repeated-free substrings in text

Problems such as "the longest stretch with no repeated character" or "the shortest stretch that contains all of these letters" are variable windows. They appear in text analysis and search features.

9. Removing duplicates from sorted data

Because equal items sit next to each other in sorted data, one pointer reads and another writes, keeping only the first of each run. The uniq command in Unix-like systems works on adjacent lines in the same way, which is why people sort first.

10. Data compression

Compression methods in the LZ77 family, which sit inside formats such as gzip, look for repeats within a sliding window of the most recent data. A repeat is stored as "go back this far and copy this many bytes", which is shorter than writing it again.

Language standard libraries

There is nothing to import for the idea itself, but many languages give you helpers that are built on it.

LanguageStructure or classWhat it is under the hoodNotes
JavaArrayDequeResizable circular arrayA queue to keep a window in order, for example the maximum of each window.
C++std::unique, std::string_viewPointer-based algorithms and a read-only view of textstd::unique removes neighbouring duplicates. string_view slices text without copying.
C#Span<T>A view onto part of an arraySlice a window without copying.
Rustslice.windows(k), Vec::dedupIterators and in-place routines over sliceswindows(k) gives every window of k items. dedup removes neighbouring duplicates.
Pythoncollections.deque, itertools.pairwiseDouble-ended queue, iterator over neighbouring pairspairwise needs Python 3.10 or newer. Slices copy, so index with two integers for big data.
GoSlices, slices.CompactA slice is a view onto an arrays[left:right] is a window with no copy. Compact removes neighbouring duplicates in Go 1.21 and newer.
JavaScriptArray.prototype.sliceCopies part of an arrayslice copies, so keep two indexes for large data.
Kotlinwindowed(k), zipWithNext()Extension functions on listswindowed(k) gives every window of k items.

The pattern: the core is two integers and a loop. The helpers save typing for fixed windows and neighbouring pairs, but variable windows are almost always written by hand.

The takeaway

Whenever a system needs an answer about the last k items, or a stretch of items next to each other, look for a sliding window. When it needs to pair up items in sorted data, it usually uses two pointers, and when the data is not sorted it uses a hash map instead.

Interview questions

Try answering out loud before you reveal hints. Interviewers care about how you reason, not just the final answer.

  1. 1.

    Given a sorted array and a target, return the positions of two numbers that add up to the target, using O(1) extra memory.

    easy
  2. 2.

    Find the length of the longest substring of a string that has no repeated characters.

    medium
  3. 3.

    Given an array of numbers and a number k, find the largest sum of k numbers in a row.

    easy
  4. 4.

    Detect whether a linked list contains a cycle, using O(1) memory.

    medium

↑ Back to top

Next lesson