Introduction
In our last lesson on the sliding window, we used two pointers moving in the same direction to define and efficiently manage a contiguous sub-array. This was our first taste of how using multiple pointers can drastically reduce time complexity, a hallmark of an expert-level solution.
Today, we will generalize this idea and master the two-pointer technique in its broader forms. Your learning outcome for this lesson is to apply the two-pointer technique (opposite-ends and fast/slow pointers) to solve problems on arrays and linked lists. We'll explore two powerful variations: one where pointers start at opposite ends and converge, and another where they move at different speeds to uncover structural properties of a data set. Mastering these patterns is non-negotiable for tackling a wide range of interview questions efficiently.
Concept 1: The Core Idea of the Two-Pointer Pattern
At its core, the two-pointer technique involves using two indices (or "pointers") to iterate through a data structure in a coordinated way. Instead of a brute-force approach with nested loops that might check every pair of elements (), this pattern allows us to process the data in a single pass, typically achieving linear time complexity ().
Technical Explanation
The strategy's effectiveness comes from how the pointers' movements are related. By moving them according to specific rules, we can intelligently explore pairs or sequences of elements, eliminating large portions of the search space with each step. The two most common variations are:
- Opposite-Ends Pointers: One pointer starts at the beginning of a sequence, and the other starts at the end. They move towards each other. This is extremely effective on sorted arrays.
- Fast & Slow Pointers: Both pointers start at the same position, but one moves faster than the other (e.g., one step vs. two steps at a time). This is the go-to pattern for problems involving linked lists, like finding cycles or middle points.

Metaphor: Two Investigators
Imagine two investigators searching a long, straight road for a clue. A brute-force approach would be for one investigator to check their position against every other possible position on the road—a very slow process. A two-pointer approach is smarter.
- Opposite-Ends: The investigators start at opposite ends of the road and walk towards each other, communicating as they go. This is efficient for finding something based on the relationship between distant points.
- Fast & Slow: One investigator walks while the other jogs. The changing distance between them reveals information about the road itself, like whether it has a loop.
To get a feel for the variety of problems this pattern unlocks, let's start with a high-level overview.
Coding Interview Patterns - Two Pointers
The video 'Coding Interview Patterns - Two Pointers' provides a fantastic survey of when and how this technique is applied. It will help you build an intuition for recognizing where two pointers can be a good fit.
Watch the segments from 02:20 to 03:46 and 07:48 to 10:24. The first part introduces the basic idea, and the second part lists keywords and scenarios (like sorted sequences, target sums, and nested loops) that are strong hints to consider a two-pointer solution.
Concept 2: Opposite-Ends Pointers ("The Squeeze")
This variation is most powerful when applied to sorted arrays. By placing one pointer at the smallest element (left = 0) and the other at the largest (right = n-1), we can make intelligent decisions based on their sum, difference, or other properties.
Technical Explanation
The core logic is to "squeeze" the pointers towards the middle. At each step, we evaluate the elements at arr[left] and arr[right].
- If the pair's property is less than our target (e.g.,
arr[left] + arr[right] < target), we know we need a larger value. Sincearr[right]is already the largest available, our only option is to move theleftpointer inward (left++) to get a slightly larger number. - If the property is greater than our target, we need a smaller value. We move the
rightpointer inward (right--). - This continues until the pointers meet or cross, having explored all viable pairs in time.
Metaphor: Finding a Book by Weight
Imagine you have a line of books sorted by weight, from lightest to heaviest. You need to find two books that have a combined weight of exactly 1kg. You pick up the lightest book (left pointer) and the heaviest book (right pointer) and weigh them together.
- If they are too heavy (> 1kg), you put the heaviest book back and try the second-heaviest (move right pointer inward).
- If they are too light (< 1kg), you put the lightest book back and try the second-lightest (move left pointer inward).
You systematically zero in on the correct pair without weighing every possible combination.
Python Code Snippet: Squares of a Sorted Array
Let's tackle a common interview question: Given a sorted array nums, return an array of the squares of each number, also sorted. A naive solution would be to square every element and then sort the result, which is . We can do better.
The largest squared values will come from the numbers with the largest absolute values, which are at the ends of the sorted input array. This is a perfect setup for the opposite-ends two-pointer approach.
2 Pointers Algorithm - DSA Course in Python Lecture 12
The video from Greg Hogg provides an excellent, detailed walkthrough for solving this exact problem in O(N) time using the two-pointer 'squeeze' technique.
Watch from 00:58 to 08:09. Pay close attention to how he identifies that the largest squared values must be on the 'outside' of the array and how he uses the left and right pointers to build the result array from largest to smallest.
from typing import List
def sortedSquares(nums: List[int]) -> List[int]:
"""
Given a sorted array, return an array of the squares of each number, also sorted.
This demonstrates the opposite-ends two-pointer technique.
Time Complexity: O(N)
Space Complexity: O(N) (for the result array, which is unavoidable)
"""
n = len(nums)
result = [0] * n # Pre-allocate result array
left, right = 0, n - 1
# We will fill the result array from the end (largest values first)
for i in range(n - 1, -1, -1):
# Compare the absolute values to find the larger square
if abs(nums[left]) > abs(nums[right]):
result[i] = nums[left] ** 2
left += 1
else:
result[i] = nums[right] ** 2
right -= 1
return result
# Example:
nums = [-4, -1, 0, 3, 10]
print(f"Sorted squares of {nums} are: {sortedSquares(nums)}") # Expected: [0, 1, 9, 16, 100]
Concept 3: In-Place Modification with Two Pointers
The opposite-ends approach is also fantastic for modifying an array or string in-place, which means without using extra memory ( space). Reversing a string is a classic example.
Technical Explanation
You place a pointer at the start (left) and another at the end (right). In a loop that runs while left < right, you simply swap the elements at the two pointer locations, and then move both pointers one step closer to the center.
Metaphor: Flipping a Deck of Cards
Imagine you have a line of cards laid out on a table. To reverse their order in-place, you pick up the first and last cards and swap their positions. Then you pick up the second and second-to-last cards and swap them. You continue this process, moving inward from both ends, until you meet in the middle.
Python Code Snippet: Reverse a String
Master the Two Pointers Pattern
The Medium article 'Master the Two Pointers Pattern' has a concise section on this exact application. It provides clean, easy-to-understand code for both reversing an array and a string.
Read the section '3. Reversing Arrays and Strings'. Note how Python's tuple unpacking a, b = b, a makes the swap operation very clean.
Here's the implementation for reversing a string. Note that since strings are immutable in Python, we first convert the string to a list of characters to perform the in-place swap.
def reverseString(s: List[str]) -> None:
"""
Reverses a list of characters in-place.
Time Complexity: O(N)
Space Complexity: O(1)
"""
left, right = 0, len(s) - 1
while left < right:
# Python's elegant way to swap two variables
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
# Example:
my_string = ["h", "e", "l", "l", "o"]
reverseString(my_string)
print(f"The reversed string is: {my_string}") # Expected: ['o', 'l', 'l', 'e', 'h']
Test your understanding!
Why is the opposite-ends two-pointer technique particularly effective on sorted arrays for problems like "Two Sum"? What would happen if you tried it on an unsorted array?
Show answer
The technique relies on the sorted property to make intelligent decisions. When an array is sorted, moving the left pointer to the right guarantees you get a value that is greater than or equal to the current one. Moving the right pointer left guarantees a smaller or equal value.
If the array were unsorted, moving a pointer would give you an unpredictable value. If arr[left] + arr[right] is too small, moving left to the right could give you an even smaller number, breaking the logic that allows you to systematically narrow the search space. You'd have to revert to a brute-force check or use a different data structure like a hash map.
Concept 4: Fast & Slow Pointers ("The Tortoise and Hare")
Now we switch to the second major variant. Here, both pointers typically start at the head of a linked list, but they traverse it at different speeds. The slow pointer moves one node at a time (slow = slow.next), while the fast pointer moves two nodes at a time (fast = fast.next.next).
Technical Explanation
The key insight is that the fast pointer is always pulling away from the slow pointer. The distance between them increases by one node at each step. This relative motion is what allows us to solve problems. For example, when the fast pointer reaches the end of the list, the slow pointer will be exactly at the middle.
Metaphor: The Tortoise and the Hare
In the classic fable, a tortoise and a hare race.
- To find the middle of a linear race track: When the speedy hare reaches the finish line, the slow-and-steady tortoise will have just crossed the halfway mark.
- To see if the track is a loop: If they are on a circular track, the hare will eventually run a full lap and catch up to the tortoise from behind. Their meeting proves the track is a cycle.
Python Code Snippet: Find the Middle of a Linked List
This is a perfect introductory problem for the fast/slow pointer technique.
Coding Interview Patterns - Two Pointers
The 'Coding Interview Patterns' video explains this application very clearly and intuitively.
Watch the segment from 12:28 to 13:40. The video explains exactly why the slow pointer ends up at the middle when the fast pointer reaches the end.
# Definition for singly-linked list node.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def findMiddle(head: ListNode) -> ListNode:
"""
Finds the middle node of a linked list.
If there are two middle nodes, returns the second middle node.
Time Complexity: O(N)
Space Complexity: O(1)
"""
slow, fast = head, head
# The loop condition must check both fast and fast.next
# to avoid a NullPointerException when fast is at the last node.
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# Example: 1 -> 2 -> 3 -> 4 -> 5 -> None
# slow moves: 1 -> 2 -> 3
# fast moves: 1 -> 3 -> 5 -> None (loop terminates)
# Middle is 3.
Concept 5: Advanced Application - Cycle Detection in a Linked List
This is the quintessential application of the fast/slow pointer pattern, also known as Floyd's Tortoise and Hare algorithm. It's a question you are very likely to encounter in an interview.
Technical Explanation
If a linked list contains a cycle, the fast pointer will enter the cycle first, followed by the slow pointer. Once both are inside the cycle, they are like two runners on a circular track. Since the fast pointer moves faster, it is guaranteed to eventually catch up to and meet the slow pointer. If the fast pointer ever reaches None, it means there was no cycle. The meeting of the pointers is the proof of a cycle's existence.

Coding Interview Prep Chapter 1: Introduction
The 'Coding Interview Prep' PDF provides a perfect, concise explanation of this algorithm, contrasting the optimal O(1) space solution with a less efficient O(N) space hash set approach.
Read the section '1. Linked List Cycle'. It shows both a brute-force (hash set) and the optimized Floyd's algorithm, including the Python code and a clear trade-off analysis.
Python Code Snippet: Linked List Cycle Detection
def hasCycle(head: ListNode) -> bool:
"""
Determines if a linked list has a cycle using Floyd's Tortoise and Hare algorithm.
Time Complexity: O(N)
Space Complexity: O(1)
"""
if not head:
return False
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If the pointers meet, a cycle is detected.
if slow == fast:
return True
# If the loop finishes, fast reached the end, so no cycle.
return False
# You can't easily create a cycle for a simple test here,
# but the logic is the key takeaway.
Conclusion
Today you've added two powerful variations of the two-pointer technique to your arsenal. This pattern is fundamental to writing efficient, optimized code for a vast category of array and linked list problems. Recognizing when to apply it is a skill that separates good programmers from great ones.
Key Takeaways:
- Two-Pointer Mindset: Always think about how you can use multiple pointers to process a data structure in a single pass, avoiding costly nested loops.
- Opposite-Ends Pointers: Your go-to strategy for sorted arrays. Use it for searching pairs, finding target values, or performing in-place modifications like reversing. The key is moving pointers inward based on a condition.
- Fast & Slow Pointers: The definitive technique for linked lists. Use it to find the middle element, detect cycles, and solve other structural problems. The key is the relative motion between the two pointers.
- Complexity Win: In almost all cases, a correctly applied two-pointer solution reduces time complexity to and often uses extra space, making it a highly optimal approach.
Preview of the Next Lesson
We've just learned how to detect if a cycle exists in a linked list. But what if the interviewer asks a follow-up: "Great. Now can you find the exact node where the cycle begins?" This is a non-trivial extension of Floyd's algorithm that requires a second phase. In our next lesson, we will dive deep into this second part, completing your mastery of Floyd's Tortoise and Hare algorithm.