Two Pointers — Interview Preparation¶
Junior Questions¶
| # | Question | Expected Answer Focus |
|---|---|---|
| 1 | What is the two-pointer technique? | Two coordinated indices over a sequence; reduces nested O(n^2) to O(n) |
| 2 | Name the three variants. | Converging (opposite ends), slow/fast (same direction), merge-style (two arrays) |
| 3 | What is the time complexity of valid-palindrome with two pointers? | O(n); each pointer moves at most n/2 steps |
| 4 | What is the space complexity? | O(1) extra; both pointers are just integers |
| 5 | Why does two-sum on a sorted array work with two pointers? | If sum < target only the left side can grow; if sum > target only the right side can shrink |
| 6 | What if the array is not sorted — can you still use two pointers for two-sum? | Sort it first (O(n log n)) OR use a hash set (O(n) avg) |
| 7 | When does sorting break two-sum? | When the problem requires original indices — sorting loses them; use a hash map |
| 8 | What is the difference between converging and slow/fast? | Converging starts at both ends and moves inward; slow/fast starts at 0 and both move forward, slow conditionally |
| 9 | What is Floyd's tortoise and hare? | Slow/fast in a linked list; slow moves 1, fast moves 2; meeting detects a cycle |
| 10 | How do you find the middle of a linked list in one pass? | Slow x1, fast x2; when fast hits end, slow is at midpoint |
| 11 | Why must at least one pointer move every iteration? | Otherwise the loop never terminates |
| 12 | What loop bound is correct for converging: l < r or l <= r? | l < r for pair problems; otherwise you pair an index with itself |
| 13 | Write the skeleton of slow/fast for removeDuplicates. | slow=1; for fast in 1..n: if nums[fast]!=nums[slow-1]: nums[slow]=nums[fast]; slow++ |
| 14 | What is the time complexity of merging two sorted arrays of size m and n? | O(m + n) |
| 15 | Can two pointers run on a linked list? | Yes — slow/fast variant especially; converging is awkward without random access |
Middle Questions¶
| # | Question | Expected Answer Focus |
|---|---|---|
| 1 | Compare two pointers vs sliding window. | Two pointers: pair from opposite ends, sorted data. Sliding window: contiguous range, often unsorted with a frequency map |
| 2 | Compare two pointers vs binary search. | Linear scan vs bisection; two pointers does not need random access; binary search needs sorted random-access data |
| 3 | Explain "container with most water" greedy move. | Move the shorter wall: width shrinks but the bounded height only increases when we discard the smaller side |
| 4 | How do you dedup 3-sum? | After sorting, skip equal neighbors on the fixed index AND on both pointers after a hit |
| 5 | Why does Dutch flag's >1 branch NOT advance mid? | The value swapped from hi has not been inspected; it could be 0, 1, or 2 |
| 6 | What is the invariant of Dutch flag? | [0..lo-1] all 0s; [lo..mid-1] all 1s; [hi+1..n-1] all 2s; [mid..hi] unsorted |
| 7 | How does "move zeros to end" preserve relative order of non-zero elements? | Slow/fast writes non-zero values in order, then fills the rest with zeros |
| 8 | Can you use two pointers to find a subarray with sum = target? | Only with non-negative values (so the sum is monotone in window length); otherwise use prefix sum + hash |
| 9 | What is the trick for isSubsequence(s, t)? | Two pointers, advance both on match, advance t on mismatch; subsequence iff i == len(s) at end |
| 10 | When is the merge-style two-pointer comparison-optimal? | Always; merging two sorted sequences requires n-1 comparisons in the worst case, which two pointers achieves |
| 11 | What is the relationship between two pointers and merge sort? | The merge step in merge sort IS variant C of two pointers |
| 12 | Give an example where two pointers fails because the data lacks monotonicity. | Two-sum on unsorted array; the sum-direction argument breaks |
| 13 | What is "expand around center" for palindromic substrings? | Outer loop over center positions; inner two-pointer expanding l--, r++ while characters match |
| 14 | Explain trapping rain water with two pointers. | Track left_max, right_max; move the side with smaller max; water above the moved cell is max - height[cell] |
| 15 | Why is two pointers inherently sequential? | Each step depends on the previous comparison's outcome — a length-n data-dependency chain |
Senior Questions¶
| # | Question | Expected Answer Focus |
|---|---|---|
| 1 | Where does two pointers appear in production database engines? | Sort-merge join inner loop; LSM compaction merges sorted SSTables; merge phase of external mergesort |
| 2 | How do you generalize merge-style two pointers to k sources? | k-way merge with a min-heap; O(N log k) total |
| 3 | What additional concerns appear when merging k distributed sorted streams? | Prefetching, per-stream batch size, backpressure, timeout handling for slow producers |
| 4 | Why is a buffered scanner (start/end pointers over a read buffer) a two-pointer pattern? | start is the slow/write cursor; end advances on each read; tokens are slices [start, end) |
| 5 | What is the memory advantage of in-place two pointers in a streaming pipeline? | Zero per-element allocation, no GC pressure; constant extra memory |
| 6 | When should you reach for sliding window instead of two pointers in a real system? | Contiguous-range aggregates (e.g., rolling sums, percentiles) where the window grows and shrinks based on a frequency-map invariant |
| 7 | How do you observe correctness of a two-pointer loop in production without per-iteration logging? | Aggregate counters: tokens emitted, ties resolved, drain leftover counts; alert if drain is unexpectedly large |
| 8 | How does two pointers compare with hash join for the equi-join operation? | Sort-merge: O((m+n) log (m+n)) prep + O(m+n) merge, sequential I/O. Hash join: O(m+n) avg but random I/O on probe; sort-merge wins on already-sorted/large data |
| 9 | What goes wrong if a single stream stalls in a k-way merge? | The min-heap can never advance past that stream's head — the whole merge stalls; use per-stream timeout |
| 10 | What is the parallel-computing limitation of two pointers? | Critical path equals the work; span = work = Theta(n); no asymptotic parallel speedup |
| 11 | How do you partition a two-pointer compaction across multiple cores? | Chunk the input range, partition independently in each chunk, then concatenate; not strictly correct for converging variants |
| 12 | What hardware behavior makes two pointers fast in practice? | Sequential memory access -> prefetcher predicts loads; tight inner loop -> branch prediction works; small loop body fits in I-cache |
| 13 | Describe a real-world failure caused by forgetting to drain leftovers in merge. | Logs cut off after the shorter source ends, dropping all later events from the longer source |
Professional Questions¶
| # | Question | Expected Answer Focus |
|---|---|---|
| 1 | Prove correctness of two-sum on sorted array with loop invariants. | Invariant I: any valid (i, j) satisfies l <= i* and j* <= r. Show initialization, maintenance (3 cases), and termination via phi = r - l |
| 2 | Show the amortized argument for total O(n) work. | Define potential phi = r - l (or i + j); phi is non-negative integer, strictly decreases (or increases) per iteration; total iterations bounded by n |
| 3 | Prove the merge of two sorted sequences requires at least n - 1 comparisons. | Adversary argument: alternating output forces a comparison between every adjacent A-B pair in the output |
| 4 | Is two pointers comparison-optimal for sorted two-sum? | No. The decision lower bound is Omega(log n), achieved by binary search; two pointers is Theta(n) |
| 5 | Then why prefer two pointers in practice? | Sequential cache behavior, no random access required, optimal for bulk-output and stream workloads |
Coding Challenge — Container With Most Water¶
Problem. Given an integer array
heightsof lengthn, whereheights[i]is the height of a vertical line at indexi, find two lines that, together with the x-axis, form a container holding the most water. Return the maximum amount of water it can store.Formally, maximize
min(heights[i], heights[j]) * (j - i)over all0 <= i < j < n.
Approach. Converging two pointers. At every step, the area is min(h[l], h[r]) * (r - l). Moving the taller wall inward shrinks the width without raising the height bound — area can only go down. So we must move the shorter wall inward. Track the maximum area seen.
Time: O(n). Space: O(1).
Invariant. The optimum (l*, r*) satisfies l <= l* < r* <= r. Justification: when h[l] < h[r], every pair (l, k) for k < r has width k - l < r - l AND bounded height <= h[l], so its area <= h[l] * (r - l), which is the current area we just considered. So l cannot be part of a strictly-better answer; discarding it is safe.
Go¶
package main
import "fmt"
// MaxArea returns the largest area of a container formed by two of the
// vertical lines in heights.
// Time: O(n). Space: O(1).
func MaxArea(heights []int) int {
l, r := 0, len(heights)-1
best := 0
for l < r {
width := r - l
var h int
if heights[l] < heights[r] {
h = heights[l]
l++ // discard the shorter wall
} else {
h = heights[r]
r--
}
if area := width * h; area > best {
best = area
}
}
return best
}
func main() {
fmt.Println(MaxArea([]int{1, 8, 6, 2, 5, 4, 8, 3, 7})) // 49
fmt.Println(MaxArea([]int{1, 1})) // 1
fmt.Println(MaxArea([]int{4, 3, 2, 1, 4})) // 16
}
Java¶
public class Solution {
/**
* Largest area of a container formed by two of the vertical lines.
* Time: O(n). Space: O(1).
*/
public static int maxArea(int[] heights) {
int l = 0, r = heights.length - 1;
int best = 0;
while (l < r) {
int width = r - l;
int h;
if (heights[l] < heights[r]) {
h = heights[l];
l++; // discard the shorter wall
} else {
h = heights[r];
r--;
}
int area = width * h;
if (area > best) best = area;
}
return best;
}
public static void main(String[] args) {
System.out.println(maxArea(new int[]{1, 8, 6, 2, 5, 4, 8, 3, 7})); // 49
System.out.println(maxArea(new int[]{1, 1})); // 1
System.out.println(maxArea(new int[]{4, 3, 2, 1, 4})); // 16
}
}
Python¶
def max_area(heights: list[int]) -> int:
"""
Largest area of a container formed by two of the vertical lines.
Time: O(n). Space: O(1).
"""
l, r = 0, len(heights) - 1
best = 0
while l < r:
width = r - l
if heights[l] < heights[r]:
h = heights[l]
l += 1 # discard the shorter wall
else:
h = heights[r]
r -= 1
area = width * h
if area > best:
best = area
return best
if __name__ == "__main__":
print(max_area([1, 8, 6, 2, 5, 4, 8, 3, 7])) # 49
print(max_area([1, 1])) # 1
print(max_area([4, 3, 2, 1, 4])) # 16
Edge Cases the Interviewer Will Probe¶
| Input | Expected | Pitfall |
|---|---|---|
[1, 1] | 1 | Smallest valid input |
[1] | 0 (or error) | No pair possible — depends on contract |
[] | 0 (or error) | Empty array |
[4, 3, 2, 1, 4] | 16 | Plateau values; both walls equal — does the answer depend on which one moves? (it does not, but you should explain why) |
| All equal heights | (n - 1) * h | Best pair is always the endpoints |
| Strictly increasing | (n - 1) * heights[0] | Bounded by the first (shortest) element |
Follow-Ups the Interviewer May Ask¶
- What if you have to return the indices, not just the area? Track
(best, best_l, best_r)together. - What if
nis huge and the array is streamed? You cannot use converging pointers (no random access from the right). You would need O(n) extra memory to buffer, or accept an approximate answer. - Can you parallelize this? The data dependency chain makes the converging variant un-parallelizable; you would split the array into chunks, find the best within each, then combine (which is incorrect because the optimum pair can span chunks). A correct parallel version requires more care.
- What is the relation to "trapping rain water"? Same input format, different output. Trapping rain water also has a two-pointer solution (move the side with the smaller
max-so-far) and an alternative monotonic-stack solution (see topic 13). - Could you do it with a stack or DP instead? Yes, but both are O(n) space and conceptually heavier. Two pointers is the textbook answer because it is
O(1)space.
The interviewer is checking that you can name the greedy invariant ("move the shorter side"), justify why it preserves the optimum, write the loop cleanly, and identify the edge cases without prompting.