Two Pointers Technique in C: Complete Guide
Two Pointers Technique in C: A Comprehensive Tutorial
The Two Pointers Technique is a simple yet powerful strategy where you use two indices (pointers) that traverse a data structure—such as an array, list, or string—either toward each other or in the same direction to solve problems more efficiently. It is one of the most versatile algorithmic patterns, appearing frequently in coding interviews and competitive programming.
This tutorial provides an in-depth exploration of the Two Pointers Technique in C: core concepts, when to use it, the classic Two Sum problem, opposite-direction and same-direction variants, sliding window, fast/slow pointers, practical implementation, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is the Two Pointers Technique?
The Two Pointers Technique uses two index variables (pointers) to traverse a data structure, typically moving them based on a condition. This reduces time complexity from O(n²) to O(n) in many problems.
Why Use Two Pointers?
- Efficiency: Reduces O(n²) to O(n) for many problems.
- Space efficiency: Typically O(1) extra space.
- Versatility: Applies to arrays, strings, linked lists.
- Simplicity: Once mastered, the pattern is easy to recognize.
- Interview favorite: Common in technical interviews.
When to Use Two Pointers
| Scenario | Example Problem |
|---|---|
| Sorted input | Two Sum in sorted array, pair with target sum |
| Pairs or subarrays | Longest substring without repeats, palindrome check |
| Sliding window | Smallest subarray with sum ≥ K, max consecutive ones |
| Linked lists (slow/fast) | Cycle detection, middle node, palindrome |
Two Main Directions
| Direction | Pointers Start | Move Toward |
|---|---|---|
| Opposite direction | Both ends | Each other |
| Same direction | Same end | Same direction (fast/slow) |
Syntax & Core Mechanics
Opposite-Direction Pattern
Pointers start at both ends and move toward each other.
Template:
c
int left = 0, right = n - 1; while (left < right) { /* Process arr[left], arr[right] */ if (condition) { left++; } else { right--; } } Same-Direction Pattern (Slow/Fast)
Both pointers move in the same direction, with one ahead of the other.
Template:
c
int slow = 0; for (int fast = 0; fast < n; fast++) { if (condition(arr[fast])) { /* Process with slow */ slow++; } } Classic Problem: Two Sum in Sorted Array
Problem: Given a sorted array and target, find if any pair sums to target.
Naive: O(n²) — nested loops.
Two Pointers: O(n) — pointers from both ends.
Key insight:
- If
arr[left] + arr[right] < target: increase sum by movingleftright. - If
arr[left] + arr[right] > target: decrease sum by movingrightleft. - If equal: found the pair.
Naive Approach
c
#include <stdio.h> #include <stdbool.h> bool twoSumNaive(const int arr[], int n, int target) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[i] + arr[j] == target) { return true; } } } return false; } int main(void) { int arr[] = {10, 20, 35, 50}; int n = sizeof(arr) / sizeof(arr[0]); int target = 70; printf("%s\n", twoSumNaive(arr, n, target) ? "true" : "false"); return 0; } Expected output:
text
true
Time: O(n²), Space: O(1)
Two Pointers Approach
c
#include <stdio.h> #include <stdbool.h> bool twoSum(const int arr[], int n, int target) { int left = 0, right = n - 1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) { return true; } else if (sum < target) { left++; /* Need larger sum */ } else { right--; /* Need smaller sum */ } } return false; } int main(void) { int arr[] = {10, 20, 35, 50}; int n = sizeof(arr) / sizeof(arr[0]); int target = 70; printf("%s\n", twoSum(arr, n, target) ? "true" : "false"); return 0; } Expected output:
text
true
Time: O(n), Space: O(1)
Why It Works (Correctness Proof)
Case 1: sum < target, move left++
arr[left]is too small to pair with any element ≤arr[right].- Skipping it is safe because no valid pair can include it.
Case 2: sum > target, move right--
arr[right]is too large to pair with any element ≥arr[left].- Skipping it is safe.
Case 3: sum == target
- Pair found.
Since we never skip a valid pair, the algorithm is correct.
Same-Direction Example: Remove Duplicates from Sorted Array
c
#include <stdio.h> int removeDuplicates(int arr[], int n) { if (n <= 1) return n; int slow = 0; for (int fast = 1; fast < n; fast++) { if (arr[fast] != arr[slow]) { slow++; arr[slow] = arr[fast]; } } return slow + 1; /* New length */ } int main(void) { int arr[] = {1, 1, 2, 2, 3, 4, 4, 5}; int n = sizeof(arr) / sizeof(arr[0]); int newLen = removeDuplicates(arr, n); printf("New length: %d\n", newLen); printf("Array: "); for (int i = 0; i < newLen; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; } Expected output:
text
New length: 5 Array: 1 2 3 4 5
Time: O(n), Space: O(1)
Palindrome Check with Two Pointers
c
#include <stdio.h> #include <string.h> #include <stdbool.h> bool isPalindrome(const char str[]) { int left = 0; int right = strlen(str) - 1; while (left < right) { if (str[left] != str[right]) { return false; } left++; right--; } return true; } int main(void) { printf("racecar: %s\n", isPalindrome("racecar") ? "true" : "false"); printf("hello: %s\n", isPalindrome("hello") ? "true" : "false"); return 0; } Expected output:
text
racecar: true hello: false
Full Practical Code Example with Explanation
This program demonstrates multiple Two Pointers applications in a single interactive tool.
c
/** * two_pointers_demo.c * Demonstrates the Two Pointers Technique across multiple problems. * Covers Two Sum, closest pair, palindrome, remove duplicates, * move zeros, container with most water, and three sum. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <errno.h> #define MAX_LINE_LEN 100 #define MAX_SIZE 100000 /* Function prototypes */ bool twoSum(const int arr[], int n, int target); void twoSumIndices(const int arr[], int n, int target, int *i, int *j); bool closestTwoSum(const int arr[], int n, int target, int *bestI, int *bestJ); bool isPalindrome(const char str[]); int removeDuplicates(int arr[], int n); void moveZerosToEnd(int arr[], int n); int maxWater(const int height[], int n); int threeSumCount(int arr[], int n, int target); int compareInts(const void *a, const void *b); int readInteger(const char *prompt, int *outValue); void printArray(const int arr[], int n); void demoTweSum(void); void demoClosestPair(void); void demoPalindrome(void); void demoRemoveDuplicates(void); void demoMoveZeros(void); void demoContainerWater(void); void demoThreeSum(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Two Pointers Technique Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Two Sum in Sorted Array\n"); printf("2. Closest Pair to Target\n"); printf("3. Palindrome Check\n"); printf("4. Remove Duplicates from Sorted Array\n"); printf("5. Move Zeros to End\n"); printf("6. Container with Most Water\n"); printf("7. Three Sum Count\n"); printf("8. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demoTweSum(); break; case 2: demoClosestPair(); break; case 3: demoPalindrome(); break; case 4: demoRemoveDuplicates(); break; case 5: demoMoveZeros(); break; case 6: demoContainerWater(); break; case 7: demoThreeSum(); break; case 8: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Two Sum: returns true if pair exists */ bool twoSum(const int arr[], int n, int target) { int left = 0, right = n - 1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) return true; if (sum < target) left++; else right--; } return false; } /* Two Sum: returns indices */ void twoSumIndices(const int arr[], int n, int target, int *i, int *j) { int left = 0, right = n - 1; *i = -1; *j = -1; while (left < right) { int sum = arr[left] + arr[right]; if (sum == target) { *i = left; *j = right; return; } if (sum < target) left++; else right--; } } /* Closest pair to target */ bool closestTwoSum(const int arr[], int n, int target, int *bestI, int *bestJ) { if (n < 2) return false; int left = 0, right = n - 1; int bestDiff = abs(arr[0] + arr[n - 1] - target); *bestI = 0; *bestJ = n - 1; while (left < right) { int sum = arr[left] + arr[right]; int diff = abs(sum - target); if (diff < bestDiff) { bestDiff = diff; *bestI = left; *bestJ = right; } if (sum < target) left++; else if (sum > target) right--; else return true; /* Exact match */ } return false; } /* Palindrome check */ bool isPalindrome(const char str[]) { int left = 0; int right = (int)strlen(str) - 1; while (left < right) { if (str[left] != str[right]) return false; left++; right--; } return true; } /* Remove duplicates */ int removeDuplicates(int arr[], int n) { if (n <= 1) return n; int slow = 0; for (int fast = 1; fast < n; fast++) { if (arr[fast] != arr[slow]) { slow++; arr[slow] = arr[fast]; } } return slow + 1; } /* Move zeros to end */ void moveZerosToEnd(int arr[], int n) { int slow = 0; for (int fast = 0; fast < n; fast++) { if (arr[fast] != 0) { int temp = arr[slow]; arr[slow] = arr[fast]; arr[fast] = temp; slow++; } } } /* Container with most water */ int maxWater(const int height[], int n) { int left = 0, right = n - 1; int maxArea = 0; while (left < right) { int h = (height[left] < height[right]) ? height[left] : height[right]; int width = right - left; int area = h * width; if (area > maxArea) maxArea = area; if (height[left] < height[right]) left++; else right--; } return maxArea; } /* Three Sum Count: count triplets summing to target */ int compareInts(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } int threeSumCount(int arr[], int n, int target) { qsort(arr, n, sizeof(int), compareInts); int count = 0; for (int i = 0; i < n - 2; i++) { int left = i + 1, right = n - 1; while (left < right) { int sum = arr[i] + arr[left] + arr[right]; if (sum == target) { count++; left++; right--; } else if (sum < target) { left++; } else { right--; } } } return count; } /* Print array */ void printArray(const int arr[], int n) { printf("["); for (int i = 0; i < n; i++) { printf("%d", arr[i]); if (i < n - 1) printf(", "); } printf("]\n"); } /* Demos */ void demoTweSum(void) { printf("\n=== Two Sum in Sorted Array ===\n"); int arr1[] = {10, 20, 35, 50}; int arr2[] = {10, 20, 30}; int arr3[] = {-8, 1, 4, 6, 10, 45}; printf("arr1: "); printArray(arr1, 4); printf(" Target 70: %s\n", twoSum(arr1, 4, 70) ? "true" : "false"); printf("arr2: "); printArray(arr2, 3); printf(" Target 70: %s\n", twoSum(arr2, 3, 70) ? "true" : "false"); printf("arr3: "); printArray(arr3, 6); printf(" Target 16: %s\n", twoSum(arr3, 6, 16) ? "true" : "false"); int i, j; twoSumIndices(arr1, 4, 70, &i, &j); if (i != -1) { printf("Pair in arr1: (%d, %d) at indices (%d, %d)\n", arr1[i], arr1[j], i, j); } } void demoClosestPair(void) { printf("\n=== Closest Pair to Target ===\n"); int arr[] = {1, 3, 5, 8, 12, 15}; int target = 10; int i, j; printf("Array: "); printArray(arr, 6); printf("Target: %d\n", target); closestTwoSum(arr, 6, target, &i, &j); printf("Closest pair: (%d, %d) with sum %d\n", arr[i], arr[j], arr[i] + arr[j]); } void demoPalindrome(void) { printf("\n=== Palindrome Check ===\n"); const char *tests[] = {"racecar", "hello", "madam", "level", "abcba"}; int count = 5; for (int i = 0; i < count; i++) { printf("\"%s\": %s\n", tests[i], isPalindrome(tests[i]) ? "palindrome" : "not palindrome"); } } void demoRemoveDuplicates(void) { printf("\n=== Remove Duplicates from Sorted Array ===\n"); int arr[] = {1, 1, 2, 2, 3, 4, 4, 5}; int n = 8; printf("Original: "); printArray(arr, n); int newLen = removeDuplicates(arr, n); printf("New length: %d\n", newLen); printf("Array: "); printArray(arr, newLen); } void demoMoveZeros(void) { printf("\n=== Move Zeros to End ===\n"); int arr[] = {0, 1, 0, 3, 12, 0, 5}; int n = 7; printf("Before: "); printArray(arr, n); moveZerosToEnd(arr, n); printf("After: "); printArray(arr, n); } void demoContainerWater(void) { printf("\n=== Container with Most Water ===\n"); int height[] = {1, 8, 6, 2, 5, 4, 8, 3, 7}; int n = 9; printf("Heights: "); printArray(height, n); printf("Max water: %d\n", maxWater(height, n)); } void demoThreeSum(void) { printf("\n=== Three Sum Count ===\n"); int arr[] = {-1, 0, 1, 2, -1, -4}; int n = 6; int target = 0; printf("Array: "); printArray(arr, n); printf("Target: %d\n", target); int count = threeSumCount(arr, n, target); printf("Triplets summing to %d: %d\n", target, count); } /* Read integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) return -1; *outValue = (int)val; return 0; } Expected Output (sample run)
text
=== Two Pointers Technique Demo === --- Menu --- 1. Two Sum in Sorted Array 2. Closest Pair to Target 3. Palindrome Check 4. Remove Duplicates from Sorted Array 5. Move Zeros to End 6. Container with Most Water 7. Three Sum Count 8. Exit Enter choice: 1 === Two Sum in Sorted Array === arr1: [10, 20, 35, 50] Target 70: true arr2: [10, 20, 30] Target 70: false arr3: [-8, 1, 4, 6, 10, 45] Target 16: true Pair in arr1: (20, 50) at indices (1, 3) --- Menu --- ... Enter choice: 5 === Move Zeros to End === Before: [0, 1, 0, 3, 12, 0, 5] After: [1, 3, 12, 5, 0, 0, 0] --- Menu --- ... Enter choice: 6 === Container with Most Water === Heights: [1, 8, 6, 2, 5, 4, 8, 3, 7] Max water: 49 --- Menu --- ... Enter choice: 7 === Three Sum Count === Array: [-1, 0, 1, 2, -1, -4] Target: 0 Triplets summing to 0: 2
Common Pitfalls, Edge Cases & Memory Considerations
1. Unsorted Array in Two Sum
The two-pointer approach requires a sorted array. Sort first (O(n log n)) or use a hash map.
2. Overflow in Sum
arr[left] + arr[right] can overflow int for large values.
c
long long sum = (long long)arr[left] + arr[right];
3. Off-by-One in Loop Condition
Use left < right (not <=) for opposite-direction pointers. Using <= causes self-comparison.
4. Move Wrong Pointer
Moving the wrong pointer breaks correctness and can cause infinite loops.
c
if (sum < target) left++; /* Correct */ if (sum > target) right--; /* Correct */
5. Duplicate Handling in Three Sum
For counting unique triplets, skip duplicates.
c
if (i > 0 && arr[i] == arr[i-1]) continue;
6. Palindrome with Non-Alphanumeric Characters
Real-world palindrome checks ignore punctuation, spaces, and case.
c
while (left < right && !isalnum(str[left])) left++; while (left < right && !isalnum(str[right])) right--; if (tolower(str[left]) != tolower(str[right])) return false;
7. Container with Most Water: Move Correct Pointer
Always move the pointer at the shorter line (to potentially find a taller one).
8. Empty or Single-Element Arrays
Return early for n < 2.
9. Same-Direction: Slow/Fast Initialization
Ensure slow and fast don’t cross unintentionally.
10. Memory Considerations
- Two pointers uses O(1) extra space.
- Sorting (when needed) is O(n log n) time, O(1) or O(n) space.
- No dynamic allocation in typical two-pointer problems.
- Use
size_tfor large array indices.
Summary & Best Practices
The Two Pointers Technique is a versatile pattern that reduces many O(n²) problems to O(n). Mastering it opens the door to efficient solutions across arrays, strings, and linked lists.
Key Takeaways
- Two pointers move either toward each other or in the same direction.
- Opposite direction: Two Sum, palindrome, container with water.
- Same direction (slow/fast): Remove duplicates, move zeros.
- Fast/slow on linked lists: Cycle detection, middle node.
- Sliding window: Subarray/substring problems.
- Requires sorted input for most opposite-direction problems.
Best Practices
- Identify the pattern (opposite or same direction) before coding.
- Sort the array if the problem requires it (Two Sum, Three Sum).
- Use
long longfor sum computations to avoid overflow. - Use
left < right(not<=) for opposite-direction loops. - Move the correct pointer based on the comparison.
- Skip duplicates in Three Sum and similar problems.
- Handle edge cases: empty, single element, all equal.
- Verify correctness with brute force during testing.
- Document the invariant (what’s guaranteed at each step).
- Profile for large inputs.
By mastering the Two Pointers Technique, you’ll solve a wide range of array and string problems efficiently and gain a powerful tool for coding interviews.












