BeginnerPython · 25 min

Two Pointers: Beginner

Here is what nobody tells you about two-pointer problems in data engineering interviews: they are the easiest pattern to get a perfect score on, and most candidates still blow them. Not because the problems are hard. Because candidates do not recognize the pattern fast enough and default to brute-force nested loops. The interviewer watches you write O(n^2) when the sorted input is screaming 'use two pointers' and mentally writes 'weak pattern recognition' on the scorecard. This lesson teaches you to spot the pattern in seconds, understand why it works, and connect it to real data engineering work so your answer has depth beyond just solving the LeetCode problem.

What you will be able to do

Recognize the five keywords that signal a two-pointer problem
Recognize the five keywords that signal a two-pointer problem
Implement opposite-end convergence for pair-finding on sorted data
Implement opposite-end convergence for pair-finding on sorted data
Understand same-direction read/write pointers for in-place deduplication
Understand same-direction read/write pointers for in-place deduplication
Connect two pointers to merge joins, CDC diffs, and dedup in real pipelines
Connect two pointers to merge joins, CDC diffs, and dedup in real pipelines

Spotting the Pattern in 10 Seconds

Daily Life
Interviews

Recognize two-pointer problems in seconds

You are looking at a two-pointer problem when you see:
  • "sorted array" or "sorted list" anywhere in the problem
  • "find a pair" or "two elements that satisfy..."
  • "in-place" or "O(1) extra space" as a constraint
  • "remove duplicates" from sorted data
  • "merge two sorted" arrays, lists, or streams
  • "palindrome" - checking characters from both ends
Let me tell you what separates the candidates who ace this from the ones who do not. It is not coding ability. It is the first 30 seconds. The candidate who reads 'given a sorted array, find two numbers that sum to target' and immediately says 'since the input is sorted, I can use two pointers from opposite ends to find the pair in O(n) time and O(1) space' has already earned a strong signal on the pattern recognition rubric item. The candidate who starts writing a nested loop has already lost ground they will spend the rest of the interview trying to recover.
The reason two pointers works is logical elimination. When you have two pointers at opposite ends of a sorted array and their sum is too large, you know that every pair involving the right element and any left element further right is ALSO too large. You eliminate an entire column of the pair matrix in one step. That is why it is O(n) instead of O(n^2). You are not checking every pair. You are eliminating entire groups of pairs that cannot possibly work.

The Decision Tree

Before you start coding, run this checklist in your head. It takes five seconds and it determines whether two pointers is the right tool.
filter
Is the input sorted, or can you sort it without violating constraints? If yes, opposite-end pointers are on the table.
query
Are you looking for a pair or combination meeting a condition? Two pointers eliminate candidates faster than hash maps.
compress
Is the constraint O(1) extra space? This rules out hash-based approaches and points directly to two pointers.
loop
Is it a linked list asking about cycles or the middle element? Fast and slow pointers.
stream
Is it asking for a contiguous subarray optimization? Sliding window (same-direction two pointers).
Say this out loud in the interview: 'The input is sorted, so I can avoid the O(n^2) brute force by using two pointers from opposite ends. Each step eliminates one candidate, giving me O(n) time with O(1) extra space.' That sentence alone demonstrates pattern recognition, complexity awareness, and space analysis. Most candidates never say it. You should always say it.

The interviewer has a rubric item for 'identified optimal approach.' Naming the technique AND stating both time and space complexity in your first 30 seconds is how you score a 4/4 on that item. Do not make the interviewer extract this from you through follow-up questions.

What the Interviewer Is Actually Scoring

Let me pull back the curtain on the rubric. At Google, the coding interview rubric has four dimensions scored 1-4: Algorithms, Coding, Communication, and Problem-Solving. At Meta, it is similar but weighted toward Communication. For two-pointer problems specifically, here is what each score looks like. A 1 on Algorithms means you could not identify the approach at all. A 2 means you eventually got to two pointers but only after hints. A 3 means you identified two pointers on your own but did not explain the invariant. A 4 means you identified the pattern immediately, stated the invariant, and compared it to the brute force. The difference between a 3 and a 4 is one sentence: 'Because the array is sorted, each pointer movement eliminates an entire class of candidates, which is why this is O(n) instead of O(n^2).' That one sentence is worth the difference between 'hire' and 'strong hire.'
A common mistake that junior data engineers make: solving the problem silently. The interviewer is staring at you, waiting for you to think out loud, and you are typing in silence. Even if your code is perfect, a silent solve caps you at a 3 on Communication. Narrate your thinking: 'I am starting pointers at both ends because the input is sorted. The left pointer will advance when the sum is too small. The right pointer will retreat when the sum is too large.' This running commentary is not optional. It is explicitly on the rubric.
TIP
If you are unsure whether two pointers applies, start with the brute force and then optimize. Say: 'The naive approach is O(n^2) with nested loops. But because the input is sorted, I can optimize to O(n) with two pointers.' This shows structured thinking even if you did not immediately see the pattern.

Opposite-End Convergence: The Core Pattern

Daily Life
Interviews

Implement opposite-end convergence for pair-finding

This is the pattern you will use most often. Two pointers start at opposite ends of a sorted array and move toward each other. At each step, you compare the values at both pointers, make a decision (move left, move right, or return the answer), and eliminate a chunk of the search space. The pointers converge until they meet, at which point you have either found the answer or proven it does not exist.

Two Sum on Sorted Input

This is the canonical example, and it is the one you should be able to write from muscle memory. Given a sorted array and a target sum, find two numbers that add up to the target. The brute force is O(n^2): check every pair. Two pointers gives you O(n) with O(1) space.
def two_sum_sorted(nums, target):
left, right = 0, len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1 # need a larger sum, move left pointer right
else:
right -= 1 # need a smaller sum, move right pointer left
return [] # no pair found
Walk through this with a concrete example. Array: [1, 3, 5, 7, 9], target: 8. Left starts at index 0 (value 1), right starts at index 4 (value 9). Sum is 10, too big, move right to index 3 (value 7). Sum is 8, found it. Two steps instead of the ten comparisons a nested loop would need. Now imagine the array has a million elements. Two pointers still finishes in at most a million steps. Nested loops would need a trillion.

Why It Works: The Invariant

Here is what the interviewer really wants to hear. Not just that the code works, but WHY. The invariant is: at every step, the answer (if it exists) is always between the two pointers. When you move the left pointer right, you are saying 'no pair involving this left element can reach the target, because even pairing it with the largest remaining element was too small.' When you move the right pointer left, you are saying 'no pair involving this right element can work, because even pairing it with the smallest remaining element was too large.'
Saying this out loud earns you marks on the 'communication' and 'problem solving' rubric items. The interviewer does not just want working code. They want proof that you understand why it is correct. Use the word 'invariant.' Use the phrase 'we can safely eliminate.' These are the magic words.

Valid Palindrome

Same pattern, different problem. Check whether a string is a palindrome by comparing characters from both ends. Left pointer starts at 0, right at the end. If they match, move both inward. If they do not match, it is not a palindrome. This is a phone screen classic. You should solve it in under 3 minutes.
def is_palindrome(s):
# Clean: lowercase, only alphanumeric
s = ''.join(c.lower() for c in s if c.isalnum())
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
Weak Answer
  • Reverses the string and compares: s == s[::-1]
  • Works but uses O(n) extra space for the reversed copy
  • Does not demonstrate the two-pointer pattern
Strong Answer
  • Two pointers from opposite ends
  • O(n) time, O(1) extra space (after cleaning)
  • Demonstrates the converging-pointer pattern explicitly

The s[::-1] trick works and is Pythonic. But in an interview testing two-pointer skills, using it misses the point. The interviewer asked the question to see if you know the pattern. Show the pattern. You can mention s[::-1] as a 'Python shortcut' after demonstrating the two-pointer approach.

Edge Cases to Handle Before Coding

Before you write any code, state the edge cases out loud. This earns you marks on the Problem-Solving rubric item. The interviewer is watching whether you think about boundaries or just dive into the happy path. For two-pointer problems, always name these:
Edge CaseWhat HappensHow to Handle
Empty arrayNo elements to compareReturn empty / False immediately
Single elementNo pair possibleleft < right condition fails, returns correctly
All duplicates[5, 5, 5, 5], target 10Both pointers converge correctly, finds 5+5
No valid answerTarget impossible for the rangePointers converge and cross, return empty
Negative numbers[-3, -1, 0, 2, 4], target 1Works unchanged, sorted order still holds

Same-Direction: Read and Write Pointers

Daily Life
Interviews

Use read/write pointers for in-place deduplication

The second two-pointer sub-pattern has both pointers starting at the same end and moving in the same direction. One pointer reads through the input. The other pointer marks the write position in the output. The read pointer moves every step. The write pointer only moves when a valid element is found. This is the pattern for in-place array modifications: removing duplicates, removing a specific value, moving zeroes to the end.

Remove Duplicates from Sorted Array

Given a sorted array, remove duplicates in-place and return the new length. This is LeetCode 26, and it is asked at every level of data engineering interviews because it maps directly to deduplication in pipelines. The read pointer scans forward. The write pointer only advances when a new unique value is found.
def remove_duplicates(nums):
if not nums:
return 0
write = 0 # position of last unique element
for read in range(1, len(nums)):
if nums[read] != nums[write]:
write += 1
nums[write] = nums[read]
return write + 1 # length of unique portion
The invariant: everything at index 0 through write is unique. The read pointer discovers new unique values. When it finds one, it copies it to write+1 and advances write. When it finds a duplicate, it just moves on. The write pointer only moves for unique elements, so duplicates are overwritten by the next unique value.
Walk through it: [1, 1, 2, 2, 3]. Write starts at 0 (value 1). Read starts at 1 (value 1). Same as write, skip. Read moves to 2 (value 2). Different from write, so write advances to 1 and copies 2. Read moves to 3 (value 2). Same as write, skip. Read moves to 4 (value 3). Different, write advances to 2 and copies 3. Result: [1, 2, 3, ...], length 3.

Move Zeroes

Given an array, move all zeroes to the end while maintaining the relative order of non-zero elements. This is LeetCode 283, a Meta phone screen classic. Same read/write pattern: the write pointer tracks where the next non-zero should go.
def move_zeroes(nums):
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
The swap is the key detail. When read finds a non-zero, it swaps with the write position and both advance. When read finds a zero, only read advances. The result: all non-zeroes are pushed to the front in their original order, and zeroes accumulate at the end.

Why This Matters for Data Engineering

This read/write pointer pattern is exactly how deduplication works on sorted data in a pipeline. You have a sorted stream of records (sorted by event_id or timestamp). You walk through with a read cursor and a write cursor. When the read cursor sees a new unique record, the write cursor accepts it. When it sees a duplicate, it skips. The result is a deduplicated output in O(n) time with O(1) extra memory. This is what ROW_NUMBER() OVER (PARTITION BY key ORDER BY ts) WHERE rn = 1 does under the hood, but you can implement it in Python for cases where SQL is not available.
Do
  • Name the sub-pattern: 'This is a read/write pointer dedup'
  • State the invariant: 'everything left of write is unique'
  • Handle the empty array case first
  • Walk through a 5-element example before claiming correctness
Don't
  • Use a hash set when the input is sorted (wastes O(n) space)
  • Forget to return the new length, not the array
  • Start coding without stating your approach
  • Use list comprehension / set() when the interviewer asks for in-place
TIP
When you solve a dedup problem in an interview, say: 'This is the same pattern as deduplication on sorted event streams in a pipeline. The read pointer scans, the write pointer emits unique records.' Connecting the algorithm to your actual work is what makes the interviewer write 'strong data engineering judgment' on the scorecard.

Fast and Slow Pointers

Daily Life
Interviews

Implement fast/slow pointers for cycle detection

The third sub-pattern: two pointers moving at different speeds. The slow pointer moves one step at a time. The fast pointer moves two steps. This is Floyd's cycle detection algorithm, also called the tortoise and hare. If there is a cycle, the fast pointer will eventually lap the slow pointer and they will meet. If there is no cycle, the fast pointer reaches the end. This pattern shows up in linked list problems and is good to know for phone screens.

Detect a Cycle in a Linked List

def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next # one step
fast = fast.next.next # two steps
if slow is fast: # they met: cycle exists
return True
return False # fast reached the end: no cycle
Why does this work? Imagine a circular track. If two runners start at the same point and one runs twice as fast, the fast runner will eventually lap the slow runner. The distance between them decreases by 1 each step (fast gains 1 step, but in a cycle, that means closing the gap). They are guaranteed to meet. The time complexity is O(n) because the fast pointer traverses the list at most twice before meeting the slow pointer or reaching the end.

Find the Middle of a Linked List

Same idea, simpler use. Slow moves one step, fast moves two. When fast reaches the end, slow is at the middle. This avoids the two-pass approach of counting the length first and then walking to length/2.
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # slow is at the middle
For data engineering interviews, fast/slow pointers are less common than opposite-end and read/write. But they do appear in phone screens, and knowing Floyd's algorithm by name ('tortoise and hare') signals that you have studied algorithms beyond just LeetCode grinding. The interviewer may not ask a linked list problem at all. But if they do, being able to name the algorithm and explain the proof of correctness in 30 seconds is a strong signal.

The Pipeline Analogy

Here is an analogy that connects fast/slow pointers to data engineering thinking. Imagine a data pipeline as a linked list of stages. If one stage feeds back into an earlier stage (a circular dependency), you have a cycle in your DAG. Cycle detection in pipeline DAGs is conceptually the same problem as cycle detection in linked lists. Airflow prevents this at the scheduler level, but the underlying algorithm is the same fast/slow traversal.
Opposite-EndSame-DirectionFast/Slow
Opposite-End
Converging from both sides
For pair-finding on sorted data. Two Sum, Valid Palindrome, Container With Most Water. The most common sub-pattern.
Same-Direction
Read/write at different speeds
For in-place modifications. Remove Duplicates, Move Zeroes. Maps directly to deduplication in pipelines.
Fast/Slow
Different step sizes
For cycle detection and midpoint finding. Linked List Cycle, Middle of Linked List. Less common in DE interviews but good for phone screens.

When to Use Fast/Slow vs Other Approaches

The interviewer may test whether you know alternatives to Floyd's algorithm. For cycle detection in an array (not a linked list), you could use a hash set to track visited indices. That is O(n) time but O(n) space. Floyd's is O(n) time and O(1) space. The tradeoff is space. If the interviewer asks 'can you do it in O(1) space?' that is the signal to switch from hash set to fast/slow. If the problem is on an array and space does not matter, a hash set is simpler and often preferred. If the problem is on a linked list, fast/slow is the standard because linked lists do not support random access, making a hash set awkward.
For data engineering roles, fast/slow pointer problems rarely appear beyond the phone screen. But knowing Floyd's algorithm demonstrates algorithmic breadth. If the interviewer asks 'do you know how to detect a cycle in O(1) space?' and you say 'yes, Floyd's tortoise and hare algorithm, where the slow pointer advances one step and the fast advances two, and if they meet there is a cycle,' you have just shown that your knowledge goes beyond the top 20 LeetCode problems. That breadth is what pushes a hire toward a strong hire at the L4/L5 level.

You should be able to name all three sub-patterns (opposite-end, same-direction, fast/slow) and give one example of each in under 60 seconds. If the interviewer asks 'what types of two-pointer problems are there?' this taxonomy is the answer they are looking for.

Two Pointers in Data Engineering

Daily Life
Interviews

Connect two pointers to merge joins, CDC, and dedup

Here is the thing that will make you stand out from every other candidate who can solve Two Sum: connecting the algorithm to real data engineering work. The interviewer is not just testing whether you can manipulate array indices. They are testing whether you understand that these patterns appear in the systems you build every day. When you solve a two-pointer problem and then say 'this is the same principle as a merge join in the query engine,' you have just demonstrated something most candidates never do: connecting the algorithm to the job.

Merge Two Sorted Streams

This is the single most important two-pointer application for data engineers. Two sorted datasets. Two cursors, one on each. Compare the elements at both cursors. Take the smaller one, advance that cursor. Repeat until both are exhausted. This is how merge sort works. It is how merge joins work. It is how you combine two sorted Kafka partitions into one ordered output.
def merge_sorted(a, b):
result = []
i, j = 0, 0
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
result.extend(a[i:]) # remaining from a
result.extend(b[j:]) # remaining from b
return result
When you present this solution, say: 'This is the merge step of merge sort, and it is the same algorithm that database engines use for merge joins when both inputs are sorted on the join key. In a pipeline, I use this pattern when merging sorted outputs from multiple Spark partitions or combining sorted event streams from different Kafka topics.' The interviewer just learned that you understand database internals, not just Python syntax.

CDC Diff Detection

Change Data Capture with two sorted snapshots. Yesterday's customer table (sorted by customer_id) and today's customer table (also sorted by customer_id). Walk both with pointers. If yesterday's key is less than today's, the record was DELETED. If today's key is less than yesterday's, the record was INSERTED. If the keys match, compare values to detect UPDATES. This is O(n+m) time with O(1) extra space. It is exactly how diff algorithms work and how MERGE statements operate internally.
def detect_changes(old_sorted, new_sorted, key_fn, val_fn):
changes = []
i, j = 0, 0
while i < len(old_sorted) and j < len(new_sorted):
old_key, new_key = key_fn(old_sorted[i]), key_fn(new_sorted[j])
if old_key < new_key:
changes.append(('DELETE', old_sorted[i]))
i += 1
elif old_key > new_key:
changes.append(('INSERT', new_sorted[j]))
j += 1
else: # keys match
if val_fn(old_sorted[i]) != val_fn(new_sorted[j]):
changes.append(('UPDATE', new_sorted[j]))
i += 1
j += 1
# remaining old = deletions, remaining new = insertions
changes.extend(('DELETE', r) for r in old_sorted[i:])
changes.extend(('INSERT', r) for r in new_sorted[j:])
return changes
Drop this in an interview and the interviewer's eyebrows go up. You just showed them that the two-pointer technique is not an academic exercise. It is how you would build a CDC pipeline component in Python. The code is clean, O(n+m), and handles all three change types (insert, update, delete) in a single pass. This is the kind of answer that gets 'strong hire' written on the scorecard.

Sorted-Set Intersection

Finding common keys between two sorted datasets: which users appear in both the transactions table AND the logins table? Two pointers, advance the one pointing to the smaller key. When they match, that key is in the intersection. O(n+m) time instead of O(n*m) brute force. This is a common ETL validation step: 'confirm that every fact row has a matching dimension row.'
The three DE applications to mention when solving any two-pointer problem:
  • Merge sorted streams: combining sorted outputs from distributed processing stages
  • CDC diff detection: comparing yesterday's and today's snapshots to find inserts, updates, and deletes
  • Sorted-set operations: intersection, union, and difference on sorted key sets for data validation
TIP
After solving any two-pointer problem, take 15 seconds to connect it to data engineering. 'In production, I have used this same pattern for merging sorted event streams and for CDC diff detection on dimension snapshots.' This single sentence transforms a coding answer into a data engineering answer. It is the difference between 'hire' and 'strong hire.'
PUTTING IT ALL TOGETHER

> You are in a Meta data engineering phone screen. The interviewer asks: 'Given a sorted array of integers and a target value, find two numbers that add up to the target.'

You say: 'Since the input is sorted, I can use two pointers from opposite ends. O(n) time, O(1) space. Each step eliminates one candidate by exploiting the sorted order.'
You write clean code in under 3 minutes. You walk through an example: 'left at index 0, right at index n-1. If sum is too big, move right left. If too small, move left right.'
You connect it: 'This is the same principle as a merge join in a database engine, or the merge step of external sort. In my pipelines, I use this pattern for CDC diff detection between two sorted snapshots.'
The interviewer asks a follow-up: 'What if there are duplicates?' You say: 'I skip consecutive equal elements after finding a match to avoid duplicate pairs. Same dedup logic I use in read/write pointer deduplication.'
KEY TAKEAWAYS
Spot the pattern: sorted input, pair-finding, O(1) space constraint, merge, palindrome
Three sub-patterns: opposite-end (pair finding), same-direction (read/write dedup), fast/slow (cycles)
State the invariant: 'we can safely eliminate because...' is the sentence that earns full marks
O(n) time, O(1) space: always state both complexities. The space advantage over hash maps is the point.
Connect to DE: merge joins, CDC diffs, deduplication, sorted-set operations. Say it out loud.

Two indices, one array, zero wasted comparisons

Category
Python
Difficulty
beginner
Duration
25 minutes
Challenges
0 hands-on challenges

Topics covered: Spotting the Pattern in 10 Seconds, Opposite-End Convergence: The Core Pattern, Same-Direction: Read and Write Pointers, Fast and Slow Pointers, Two Pointers in Data Engineering

Lesson Sections

  1. Spotting the Pattern in 10 Seconds (concepts: pyTwoPointers, pyPatternRecognition)

    Let me tell you what separates the candidates who ace this from the ones who do not. It is not coding ability. It is the first 30 seconds. The candidate who reads 'given a sorted array, find two numbers that sum to target' and immediately says 'since the input is sorted, I can use two pointers from opposite ends to find the pair in O(n) time and O(1) space' has already earned a strong signal on the pattern recognition rubric item. The candidate who starts writing a nested loop has already lost g

  2. Opposite-End Convergence: The Core Pattern (concepts: pyTwoPointers, pyConvergingPointers)

    This is the pattern you will use most often. Two pointers start at opposite ends of a sorted array and move toward each other. At each step, you compare the values at both pointers, make a decision (move left, move right, or return the answer), and eliminate a chunk of the search space. The pointers converge until they meet, at which point you have either found the answer or proven it does not exist. Two Sum on Sorted Input This is the canonical example, and it is the one you should be able to w

  3. Same-Direction: Read and Write Pointers (concepts: pyReadWritePointers, pyDeduplication)

    The second two-pointer sub-pattern has both pointers starting at the same end and moving in the same direction. One pointer reads through the input. The other pointer marks the write position in the output. The read pointer moves every step. The write pointer only moves when a valid element is found. This is the pattern for in-place array modifications: removing duplicates, removing a specific value, moving zeroes to the end. Remove Duplicates from Sorted Array Given a sorted array, remove dupli

  4. Fast and Slow Pointers (concepts: pyFastSlowPointers, pyFloydsCycle)

    The third sub-pattern: two pointers moving at different speeds. The slow pointer moves one step at a time. The fast pointer moves two steps. This is Floyd's cycle detection algorithm, also called the tortoise and hare. If there is a cycle, the fast pointer will eventually lap the slow pointer and they will meet. If there is no cycle, the fast pointer reaches the end. This pattern shows up in linked list problems and is good to know for phone screens. Detect a Cycle in a Linked List Why does this

  5. Two Pointers in Data Engineering (concepts: pyMergeSorted, pyCDCDiff, pySetIntersection)

    Here is the thing that will make you stand out from every other candidate who can solve Two Sum: connecting the algorithm to real data engineering work. The interviewer is not just testing whether you can manipulate array indices. They are testing whether you understand that these patterns appear in the systems you build every day. When you solve a two-pointer problem and then say 'this is the same principle as a merge join in the query engine,' you have just demonstrated something most candidat