← BlogMISTAKES — 18 Apr 2026

Four Ways I Got a Two-Pointer Loop's Bookkeeping Wrong

Four different two-pointer problems, four different ways the loop's own bookkeeping got away from me — and the same root cause underneath every one of them.

CategoryMistakes
Published18 Apr 2026
Reading time6 min
dsatwo-pointersarrays

I keep a running log of mistakes I make while grinding practice problems, mostly so I can stop telling myself each one was a one-off. This week that log stopped being useful as reassurance. Four different two-pointer problems, and when I put the diffs next to each other I couldn't pretend they were unrelated anymore. Same mistake, same blind spot, four times running.

The pattern

Looking back at all four, I noticed the same setup each time: some outer bookkeeping — an index, a target, a running comparison — and then an inner loop closing two pointers toward each other. I always put my attention on the actual math, the sum or the comparison, because that felt like the real problem to solve. And I kept getting that part right. What I kept getting wrong had nothing to do with the math at all. It was whether the loop actually moved on every pass, and whether it moved the thing I meant it to move. I don't think I even considered checking that part — it felt too basic to be where a bug would hide.

Where it kept showing up

4Sum. This one's an outer index i, a second index j, and a two-pointer sweep inside that. I got so absorbed in getting the inner left/right logic right that I never incremented i. The outer loop just sat there, re-running the same inner sweep on the same fixed pair forever. Nothing about the sum logic was wrong. The bug wasn't in a comparison, it was in a line that should have existed and didn't.

3Sum Closest, twice, in the same solve. First: an outer while loop where I again never advanced its index. Straight TLE, no crash, just a timer running out on the judge. Second, after I thought I'd fixed it: I still had

if abs(target - curr_sum) < abs(target - closest):
    closest = curr_sum
    right -= 1
    left += 1

The pointer moves were living inside the branch that updates closest. So left and right only moved on iterations where this sum happened to be a new best — which, once closest had converged near the answer, stopped happening at all. It didn't crash on bad math. It just stalled, because I'd put the one thing that's supposed to happen every iteration inside a condition that had no business being there.

Wrong pointer, right instinct. A few problems later I caught myself moving right when I meant left — sum too small, needed a bigger value, but my hand moved the pointer that shrinks the window instead of the one that grows it. Didn't infinite-loop that time, just produced comparisons against values that could never be the answer, and it took longer to notice than it should have because the code looked like a normal two-pointer sweep at a glance.

Boats to Save People, which isn't technically the same bug but lives in the same neighborhood: I wrote while l < r, which is the right loop condition, and then just... stopped there. When l and r land on the same index — one person left, who still fits in exactly one boat — the loop condition goes false and control moves on without that last person ever being counted. Different failure mode than the others, a silent undercount instead of a hang, but the same blind spot underneath: I never actually looked at what the loop leaves behind on its last pass, only at what happens while it's still running.

The fix

For the increment misses: I'm trying to write the increment or pointer-advance right next to the condition, before I write any of the actual loop logic. Not "I'll add it once the logic works" — that was the plan both times it didn't happen.

For the conditional-movement one: before I put a pointer move inside an if, I'm supposed to ask whether it's actually meant to be conditional. In every case I've hit so far, it isn't — it happens every iteration no matter what. So catching pointer-advance code sitting inside a branch is supposed to be a stop-and-check moment now. Supposed to be.

For the wrong-direction one: I say the direction out loud before I write the line. "Sum's too small, so left moves right." Whether that habit survives me rushing through a problem under time pressure, I genuinely don't know yet.

The lesson

All four of these were sitting in the same square inch of code — not the algorithm, just whether the loop is actually a loop. I write the happy path first every time because that's the part I find interesting, and the bookkeeping gets whatever attention is left over, which apparently isn't much. I'm telling myself now that I'll trace the first and last iteration by hand before calling any pointer-based loop done. I've told myself things like that before and then not actually done it, so I'm not going to pretend this time is guaranteed to be different just because I wrote a paragraph about it. But for what it's worth — every one of these four bugs was sitting at one of those two boundaries, never in the middle. That's at least where I'd look first, next time.

← The Boundary Condition I Have to Check Every Single TimeMy Most Frequent Bug Isn't a Bug, It's a Typo →

Notes from readers

Comments — via GitHub