Ometek Academy Ometek Academy
Sign in Start Training →
#two-pointers #intuition

Two pointers is monotonicity in disguise.

O
Ometek
19 July 2026 · 5 min read

Every two-pointer solution works for the same reason binary search works. Once you see the connection, you stop memorising patterns and start deriving them. Here is the one idea that ties it together.

Most explanations of two pointers teach you the pattern. Left pointer here, right pointer there, move one when some condition holds. Memorise the template and apply it.

That works until the problem changes slightly and you can't tell whether two pointers applies. Then you're guessing.

Here is the underlying idea that most explanations skip: two pointers works when the relevant property of the window is monotone with respect to the pointer movement. Moving the right pointer forward can only increase the property. Moving the left pointer forward can only decrease it. That's the same condition that makes binary search correct. Once you see that, "two pointers or binary search?" stops being a coin flip.

What monotonicity actually means here

Take the classic: find two indices i < j in a sorted array such that a[i] + a[j] = target.

Put one pointer at the left end and one at the right. The sum is a[l] + a[r]. If you move r left, the sum decreases. If you move l right, the sum increases. The sum is monotone with respect to both pointer movements. That's why you can make progress every step: if the sum is too large, move r left; if too small, move l right.

int l = 0, r = n - 1;
while (l < r) {
    int s = a[l] + a[r];
    if (s == target) { /* found */ break; }
    else if (s < target) l++;
    else r--;
}

The code is five lines. The reason it works is one sentence: the sum is monotone in l and monotone in r.

Sliding window is the same idea

Longest substring with at most K distinct characters. This is two pointers on a string, often called a sliding window. The property here is: the number of distinct characters in [l, r]. As r increases, the count can only stay the same or go up. As l increases, it can only stay the same or go down. Monotone in both directions.

int l = 0, best = 0;
map<char, int> cnt;
for (int r = 0; r < n; r++) {
    cnt[s[r]]++;
    while ((int)cnt.size() > k) {
        cnt[s[l]]--;
        if (cnt[s[l]] == 0) cnt.erase(s[l]);
        l++;
    }
    best = max(best, r - l + 1);
}

Same structure. The while loop advances l until the property is restored. This is valid because increasing l can only reduce the distinct count — it cannot make it worse.

Smallest subarray with sum at least S

The property is: sum of the window [l, r]. Monotone in r (moving right adds a positive element, assuming all elements are positive), monotone in l (moving left removes an element). The algorithm: expand r until the sum reaches S, then shrink l as far as possible while the sum stays above S, recording the window length.

Note the assumption: all elements positive. That's where the monotonicity comes from. If elements can be negative, shrinking l might decrease the sum, and the property is no longer monotone. Two pointers would produce wrong answers without any error messages. I'll come back to this.

When it silently breaks

This is the part that gets students. Two pointers doesn't crash when the monotonicity assumption is violated. It just gives wrong answers, and if your test cases are weak you won't notice.

Suppose you have an array with negative elements and you want the shortest subarray with sum at least S. Shrinking l might decrease the sum below S. But your while loop doesn't know that — it just checks the condition and stops. The result looks plausible and is wrong.

The question to ask before writing a two-pointer solution: what is the property of my window, and is it genuinely monotone as I move each pointer? Write that down explicitly. If you can't state the monotone property, you cannot verify that the algorithm is correct.

When you can name the monotone property, the pointer movement writes itself.

Two pointers versus binary search

Both require monotonicity. The difference is cost. Binary search on a function f(x) requires you to evaluate f(x) for a single point in O(log n) rounds — if evaluating f is cheap (O(1) or O(log n)), binary search is fine. Two pointers amortises the work differently: each pointer moves at most n steps total, so the overall complexity is O(n) regardless of what you do per step, as long as it's O(1).

If your property is monotone and you can maintain it incrementally as the window grows or shrinks, two pointers gives you O(n). If maintaining it incrementally is expensive but evaluating it from scratch at a fixed point is fast, binary search is cleaner. Often both work; two pointers tends to have a smaller constant.

The two-pointer pattern is not a trick to memorise. It's a consequence of one property. Find the property, check that it's monotone, and the rest of the code follows directly.

Solve these next

Three problems I recommend straight after this article. Each one forces you to name the monotone property before the code will work:

  • Codeforces 676C — Vasya and String. Both two pointers and binary search on the answer are correct here. Decide which one you're writing and say why — that decision is this whole article in miniature.
  • CSES 1660 — Subarray Sums I. All elements positive. Clean sliding window; state the property, then write it.
  • CSES 1661 — Subarray Sums II. Same statement, but negatives are allowed — and the monotonicity is gone. Notice exactly where your two-pointer reasoning breaks, then solve it a different way.

Related