-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
233: Whiteboard Practice: Two-Pointer Techniques
When you're in a technical interview and the interviewer hands you a sorted array, your brain should immediately start tingling. "Sorted" is a massive hint. It means there is an inherent order we can exploit so we don't have to look at every single possible combination of elements.
Let's look at a classic whiteboard problem: we have a sorted array of integers, and we need to find if any two numbers add up to a specific target sum. If they do, we return their indices.
The brute-force instinct
If I'm thinking quickly, my first instinct is usually just to check everything. I'll grab the first number, then loop through every other number to see if they hit the target. Then I'll move to the second number and repeat. It looks like this:
const twoSumBrute = (nums, target) => {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return null;
};
const numbers = [2, 7, 11, 15];
const target = 9;
console.log(twoSumBrute(numbers, target)); // [0, 1]
This works. But it's slow. If the array has 10,000 elements, I'm potentially doing millions of additions. I'm ignoring the most important piece of information I was given: the array is already sorted. In a real interview, this is where the interviewer would ask, "Can we do this in linear time?"
Noticing the sorted advantage
Let's think about how we'd do this by hand. If I have [2, 7, 11, 15] and my target is 22, I wouldn't start at the beginning and just guess. I'd probably look at the biggest number (15) and the smallest number (2).
15 + 2 is 17. That's too small. Since the array is sorted, I know that 2 is the smallest possible value. Adding 2 to anything else isn't going to help me get closer to 22 than adding something larger than 2 would. So, I can effectively "discard" the 2 and move inward.
Wait, let's try that in code. Instead of nested loops, I'll use two variables to keep track of my positions—one at the start and one at the end.
Squeezing the array from both sides
I'll call these left and right. I'll move them toward each other based on whether my current sum is too high or too low. It's almost like a game of "Hot or Cold."
const twoSumTwoPointer = (nums, target) => {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const currentSum = nums[left] + nums[right];
if (currentSum === target) {
return [left, right]; // Found it!
}
if (currentSum < target) {
// Sum is too small, we need a larger number.
// Since it's sorted, moving 'left' to the right increases the sum.
left++;
} else {
// Sum is too large, we need a smaller number.
// Moving 'right' to the left decreases the sum.
right--;
}
}
return null; // No pair found
};
const numbers = [2, 7, 11, 15];
const target = 26;
console.log(twoSumTwoPointer(numbers, target)); // [2, 3] (11 + 15)
I love this approach because it's clean. I'm not guessing; I'm making a logical decision at every single step. If the sum is too low, the only way to increase it is to move the left pointer up. If it's too high, the only way to decrease it is to move the right pointer down.
Why this actually saves us time
In the first version, I had a loop inside a loop. That's $O(n^2)$ time complexity. In this version, I have one while loop. In the absolute worst case, the pointers meet in the middle, meaning I've looked at each element at most once. That's $O(n)$—linear time.
Two-pointer techniques aren't just for summing numbers, though. You'll see this pattern whenever you're dealing with sorted lists, reversing strings, or even detecting cycles in linked lists. The core idea is always the same: use two indices to narrow down the search space without having to re-scan the entire collection.
📋 Practical Task
Exercise: Valid Palindrome Scanner
In this exercise, you'll apply the two-pointer technique to determine if a string is a palindrome (reads the same forwards and backwards). However, there's a catch: the string might contain spaces, punctuation, and mixed casing, which should all be ignored.
Your Task: Write a function isPalindrome(str) that:
- Uses two pointers (one at the start, one at the end) to compare characters.
- Skips any non-alphanumeric characters.
- Is case-insensitive.
- Returns
trueif it's a palindrome, otherwisefalse.
Test Case: isPalindrome("A man, a plan, a canal: Panama") should return true.
There are no comments for now.