Identifying Direct Application
What Makes a Problem a “Direct Application”?
The two-pointer technique can be applied directly when the problem asks you to do something with pairs of elements — one from each end — while moving both pointers inward until they meet. No clever transformation needed. The template fits as-is.
The mental checklist:
✅ We need to look at two positions simultaneously ✅ One position starts near the beginning, one near the end ✅ Both move inward with each iteration ✅ The work done at each step is simple (swap, compare, copy…)
If those four boxes are checked, you’re looking at a direct application.
The Canonical Example: Reverse an Array In-Place
Problem statement: Given an array arr, reverse it in-place. Do not create and return a new array — modify the original.
Input: arr = [1, 2, 3, 4, 5]
Output: arr is modified to [5, 4, 3, 2, 1]
---
config:
theme: base
themeVariables:
primaryColor: "#dbeafe"
primaryBorderColor: "#3b82f6"
primaryTextColor: "#1e3a5f"
lineColor: "#64748b"
secondaryColor: "#ede9fe"
tertiaryColor: "#fef9c3"
---
block-beta
columns 5
A["1"] B["2"] C["3"] D["4"] E["5"]
F["5"] G["4"] H["3"] I["2"] J["1"]
Reverse the array in-place — the original array (top row) becomes the reversed array (bottom row) without allocating new memory.
Brute Force: Two Passes + Temp Array
The naive approach copies elements in reverse into a temporary array, then copies back:
- Walk
arrbackwards and filltempforwards - Walk
tempforwards and copy back intoarr
---
config:
theme: base
themeVariables:
primaryColor: "#dbeafe"
primaryBorderColor: "#3b82f6"
primaryTextColor: "#1e3a5f"
lineColor: "#64748b"
secondaryColor: "#ede9fe"
tertiaryColor: "#fef9c3"
---
flowchart TB
subgraph ORIG["Original arr"]
direction LR
A["1"] --- B["2"] --- C["3"] --- D["4"] --- E["5"]
end
subgraph TEMP["temp (copy arr backwards)"]
direction LR
T0["5"] --- T1["4"] --- T2["3"] --- T3["2"] --- T4["1"]
end
subgraph BACK["arr (copy temp back)"]
direction LR
R0["5"] --- R1["4"] --- R2["3"] --- R3["2"] --- R4["1"]
end
ORIG -->|"pass 1: backwards copy"| TEMP
TEMP -->|"pass 2: forwards copy"| BACK
Brute-force reversal — two full passes and O(n) extra space for the temp array.
from typing import List
class BruteForce:
def reverse(self, arr: List[int]) -> None:
n = len(arr)
temp = [0] * n
# Pass 1: copy arr backwards into temp
for i in range(n - 1, -1, -1):
temp[n - 1 - i] = arr[i]
# Pass 2: copy temp back into arr
for i in range(n):
arr[i] = temp[i]
arr = [1, 2, 3, 4, 5]
BruteForce().reverse(arr)
print(arr) # [5, 4, 3, 2, 1]
This works, but it uses O(n) extra space and touches every element twice. We can do better.
Two-Pointer Solution: One Pass, Zero Extra Space
Key insight: to reverse an array, we just need to swap equidistant elements from both ends — arr[0] ↔ arr[n-1], arr[1] ↔ arr[n-2], and so on. Each swap needs exactly two positions: one from the left, one from the right. That’s the two-pointer template.
---
config:
theme: base
themeVariables:
primaryColor: "#dbeafe"
primaryBorderColor: "#3b82f6"
primaryTextColor: "#1e3a5f"
lineColor: "#64748b"
secondaryColor: "#ede9fe"
tertiaryColor: "#fef9c3"
---
flowchart TB
subgraph Step0["Start: left=0, right=4"]
direction LR
A0(["left"]) --> S0A["1"] --- S0B["2"] --- S0C["3"] --- S0D["4"] --- S0E["5"]
S0E --> B0(["right"])
end
subgraph Step1["Swap arr[0]↔arr[4], left=1, right=3"]
direction LR
A1(["left"]) --> S1A["5"] --- S1B["2"] --- S1C["3"] --- S1D["4"] --- S1E["1"]
S1D --> B1(["right"])
end
subgraph Step2["Swap arr[1]↔arr[3], left=2, right=2"]
direction LR
A2(["left"]) --> S2A["5"] --- S2B["4"] --- S2C["3"] --- S2D["2"] --- S2E["1"]
S2C --> B2(["right"])
end
subgraph Step3["left = right = 2 → loop ends"]
DONE(["✓ arr = [5, 4, 3, 2, 1]"])
end
Step0 -->|"swap + move"| Step1
Step1 -->|"swap + move"| Step2
Step2 -->|"left ≥ right"| Step3
Two-pointer reversal on [1, 2, 3, 4, 5] — two swaps close the gap from both ends; the middle element needs no swap.
from typing import List
class Solution:
def reverse(self, arr: List[int]) -> None:
left = 0 # Start at the leftmost element
right = len(arr) - 1 # Start at the rightmost element
# Stop when pointers meet or cross:
# - Odd-length array: left == right at the middle element — it's already in place, no swap needed
# - Even-length array: left > right means all pairs have been swapped
while left < right:
# Swap the two ends — these elements are equidistant from center
# and belong in each other's positions after reversal
arr[left], arr[right] = arr[right], arr[left]
left += 1 # This position is finalized — move inward
right -= 1 # This position is finalized — move inward
# Invariant at exit: every pair (0, n-1), (1, n-2), ... has been swapped.
# The array is fully reversed in-place.
arr = [1, 2, 3, 4, 5]
Solution().reverse(arr)
print(arr) # [5, 4, 3, 2, 1]
arr2 = [1, 2, 3, 4]
Solution().reverse(arr2)
print(arr2) # [4, 3, 2, 1]
One pass. No extra memory. The two-pointer template applied directly.
Trace — arr = [1, 2, 3, 4, 5]
arr = [1, 2, 3, 4, 5] left = 0, right = 4
Step 1 │ left=0 (1), right=4 (5) │ 0 < 4 → swap │ [5, 2, 3, 4, 1] │ left=1, right=3
Step 2 │ left=1 (2), right=3 (4) │ 1 < 3 → swap │ [5, 4, 3, 2, 1] │ left=2, right=2
Step 3 │ left=2 (3), right=2 (3) │ 2 == 2 → left < right is false → loop exits
Result: [5, 4, 3, 2, 1] ✓
Note: The middle element (3) never needed a swap — it's equidistant from both ends
and sits in its correct reversed position automatically.
Even-length check — arr = [1, 2, 3, 4]:
Step 1 │ left=0 (1), right=3 (4) │ 0 < 3 → swap │ [4, 2, 3, 1] │ left=1, right=2
Step 2 │ left=1 (2), right=2 (3) │ 1 < 2 → swap │ [4, 3, 2, 1] │ left=2, right=1
Step 3 │ left=2, right=1 │ 2 > 1 → loop exits (pointers crossed)
Result: [4, 3, 2, 1] ✓
Note: For even-length arrays the pointers cross (left > right) rather than meet —
all pairs are handled before that happens.
Fitting the Template
Let’s verify this problem matches all four checkboxes:
| Checkpoint | This Problem |
|---|---|
| Two positions at once? | ✅ arr[left] and arr[right] |
| One near start, one near end? | ✅ left=0, right=n-1 |
| Both move inward? | ✅ left++, right-- each iteration |
| Simple work per step? | ✅ One swap |
Checkpoint 1 — Why “two positions at once”?
WHAT: A reversal requires swapping pairs of elements. A swap is an inherently two-element operation — you can’t swap a single element with nothing.
WHY it means two pointers fit: Every step of the algorithm needs arr[left] and arr[right] simultaneously. One pointer alone can’t do the job — it would need to “remember” the element it picked up and go look for where to put it, which is exactly what the brute force does (it stores the whole array in temp).
What breaks with a single pointer: Walk forward with one pointer and try to reverse in-place — when you overwrite arr[0] with arr[4], the original arr[0] is gone. You’d need to save it somewhere first. That “somewhere” is the temp array. Two pointers sidestep the need entirely: they swap atomically (a, b = b, a), so nothing is lost.
Rule of thumb: If the problem needs to operate on two elements that “belong together” (a pair, a palindrome check, a sum), two simultaneous pointers are the natural fit.
Checkpoint 2 — Why “one near start, one near end”?
WHAT: left starts at index 0 (the first element), right starts at index n-1 (the last element).
WHY this specific placement: After reversal, element at index 0 must land at index n-1 and vice versa. Element at index 1 must land at n-2. The pattern is: element at distance d from the left end swaps with element at distance d from the right end.
Placing one pointer at each end directly aligns with this structure — at every step, left and right are pointing at exactly the pair that needs to swap next.
What breaks with a different placement: If both pointers started from the left (like in many sliding window problems), you’d never naturally reach the element at n-1 first. You’d be doing something fundamentally different — not a reversal.
Concrete check: [1, 2, 3, 4, 5]
left=0, right=4:1and5are the farthest pair — swap first ✓left=1, right=3:2and4are next closest pair — swap second ✓left=2, right=2: single middle element — no swap needed ✓
Checkpoint 3 — Why “both move inward”?
WHAT: After each swap, both left increments by 1 and right decrements by 1.
WHY inward movement: After swapping arr[left] and arr[right], those two positions are finalized — they now hold the correct values for a reversed array. The unsolved subproblem is the inner portion: arr[left+1 .. right-1]. Moving both pointers inward shrinks the problem by 2 each step and focuses attention on exactly what’s left to solve.
Why the stop condition is left < right (not left <= right):
- When
left == right(odd-length arrays): both pointers are at the same middle element. That element is already in the correct position — swapping it with itself is a no-op. We stop before that unnecessary step. - When
left > right(even-length arrays): the pointers have crossed, all pairs have been handled. No element remains.
What breaks if you stop at left <= right: For odd-length arrays like [1, 2, 3, 4, 5], when left = right = 2, you’d execute arr[2], arr[2] = arr[2], arr[2] — harmless but wasteful. For the stop condition to be correct, left < right is sufficient and exact.
Checkpoint 4 — Why “simple work per step”?
WHAT: At each step, we do exactly one swap: arr[left], arr[right] = arr[right], arr[left].
WHY “simple” matters: The two-pointer direct application template is powerful precisely because the loop body is O(1) — a fixed amount of work per step. Combined with O(n) steps (n/2 swaps), the total is O(n) time. The simplicity of the per-step work is what keeps the whole algorithm lean.
HOW to recognize “simple work” in other problems: The work per step should not require nested loops, searching, or recursion. It should be a direct operation on the two elements currently pointed at — compare, swap, copy, check equality. If the per-step work itself requires a loop, you may be looking at a subproblem pattern instead (section 05).
What this rules out: If you found yourself needing to scan the entire remaining array on each step, that’s O(n²) and the direct-application template no longer applies cleanly.
That’s a direct application — all four checkboxes confirmed.
Problems in This Category
The following lessons each apply the two-pointer technique in exactly this direct way. Each is a small variation on the same theme:
| Problem | Two-pointer work per step |
|---|---|
| Flip Characters | Swap characters from both ends |
| Palindrome Checker | Compare characters from both ends |
| Vowel Exchange | Find and swap vowels from both ends |
| Reverse Words | Reverse each word’s characters with inner two pointers |
| Reverse Segments | Reverse a sub-range with two pointers |
| Reverse Word Order | Reverse entire string, then reverse each word |
Each is a small twist on the same pattern — same skeleton, different work in the loop body.