The Frontend Engineer's LeetCode Blueprint, Part 1: The Linear Gauntlet
Frontend habits like map/filter/reduce chains fail LeetCode's hidden test suites. Part 1 covers V8 array internals, two pointers and sliding windows, with worked solutions for 3Sum, Minimum Window Substring and Trapping Rain Water.
Part 1: The Linear Gauntlet (Two Pointers, Sliding Windows, and V8 Realities)
The Confession
Let’s be honest with each other.
You build complex state machines in React. You configure webpack plugins in your sleep. You’ve debugged race conditions inside nested custom hooks and wrestled with WebGL pipelines. You are a senior frontend engineer.
Then you sit down for a live coding screen—or you open LeetCode to prepare for a Toptal round—and you get handed this:
"Given an array of integers
height, find two lines that together with the x-axis form a container, such that the container contains the most water."
Your brain immediately fires the frontend reflex:
const maxWater = heights.map((h, i) => ...).filter(...).reduce(...)Ten minutes later, you have three nested loops disguised as chained array prototypes, your browser tab freezes, and the console spits out: Time Limit Exceeded (TLE).
Here is the truth: Frontend programming rewards declarative, immutable, defensive abstractions. LeetCode and elite technical screenings (like Toptal's Codility gauntlet) reward imperative, mechanical sympathy, pointer manipulation, and memory layout awareness.
In this 5-part series, we are going to deconstruct algorithms through the lens of modern JavaScript. We are starting from ground zero and climbing to the top tier: solving Hard-rated problems with predictable, production-grade JavaScript that will blast through automated screening test suites.
Welcome to Part 1: The Linear Gauntlet.
1. JavaScript Engine Realities: What LeetCode Won't Tell You
Before writing a single pointer, we have to talk about how V8 (Node.js/Chrome) executes JavaScript. In LeetCode Medium/Hard challenges, an algorithm with the right theoretical Big-O can still fail due to hidden constant-factor overhead in JavaScript.
Hidden Trap #1: Array.prototype.shift() and splice()
In frontend apps, array.shift() is harmless for a dropdown list with 10 items. In LeetCode:
array.pop()runs inO(1)amortized time. It trims the tail.array.shift()runs inO(n)time. It forces the engine to re-index every single element in memory.
If you write a loop of n elements and call shift() inside it, your O(n) algorithm just silently mutated into an O(n^2) slog.
Hidden Trap #2: V8 Array Representation (Fast Elements vs. Dictionary Mode)
In V8, JavaScript arrays aren't traditional C-style contiguous memory blocks by default, but V8 tries to make them so if you keep them homogeneous:
- PACKED_SMI_ELEMENTS: Contiguous memory of 31-bit integers. Fastest.
- HOLEY_ELEMENTS: If you skip an index (
arr[100] = 5whenarr.lengthwas 2), V8 drops optimizations and may convert the array into a hash map lookup internally.
// DO NOT do this in interview problems:
const arr = [];
arr[100000] = 1; // De-optimized into a slow sparse array (Dictionary Mode)
// DO this:
const arr = new Array(100000).fill(0); // Dense, contiguous blockHidden Trap #3: Map vs. Plain Object {}
- Plain Object keys are converted to strings or symbols. Number keys become strings (
obj[1]accesses key"1"). - In high-throughput lookups (
10^5operations),new Map()or a typed array (new Int32Array(n)) avoids unnecessary string serialization and prototype lookups. If your keys are contiguous numbers or ASCII characters, a fixed-sizeUint32ArrayorArray(256).fill(0)will run 10x faster than{}.
2. Pattern 1: Two Pointers (Converging & Traversal)
Two Pointers is the art of eliminating an entire nested loop by exploiting sorted order or symmetrical properties.
The Mental Model
When you see:
- A sorted array (or a problem where sorting first takes
O(n log n)without violating space limits). - A search for pairs, triplets, or sub-segments that satisfy an inequality or sum.
- In-place array transformations with
O(1)auxiliary space.
Your default intuition shouldn't be nested loops (O(n^2)); it should be two indices closing in from opposite boundaries (O(n)).
Left -> <- Right
[ -4, -1, -1, 0, 1, 2 ]Challenge 1: 3Sum (LeetCode 15 - Medium / Toptal Classic)
Problem: Given an integer array
nums, return all the triplets[nums[i], nums[j], nums[k]]such thati != j,i != k, andj != k, andnums[i] + nums[j] + nums[k] === 0. The solution set must not contain duplicate triplets.
Why Frontenders Fail This:
Most candidates know to sort the array. But they get crushed by the duplicate triplets requirement. They use JSON.stringify() inside a Set to filter duplicates, which destroys their runtime and balloons memory to O(n^2).
The Mechanical Strategy:
- Sort the array numerically:
nums.sort((a, b) => a - b). Complexity:O(n log n). - Iterate with pointer
ifrom index0tonums.length - 3. - If
nums[i] > 0, break early. (Three positive numbers can never sum to zero in a sorted array). - If
nums[i] === nums[i - 1], skip it! This eliminates duplicates for the first element. - Use two pointers:
left = i + 1,right = nums.length - 1. - Calculate
sum = nums[i] + nums[left] + nums[right]:- If
sum === 0: Push[nums[i], nums[left], nums[right]]to results. - Then advance
leftpast any identical elements (while (nums[left] === nums[left + 1]) left++). - Advance
rightpast any identical elements (while (nums[right] === nums[right - 1]) right--). - Move both pointers inwards:
left++,right--. - If
sum < 0: We need a larger number →left++. - If
sum > 0: We need a smaller number →right--.
- If
The Code:
/**
* Time Complexity: O(n^2) - O(n log n) sort + n iterations of O(n) two-pointer sweep
* Space Complexity: O(1) auxiliary (ignoring output array & sort stack space)
* @param {number[]} nums
* @return {number[][]}
*/
function threeSum(nums) {
const result = [];
if (!nums || nums.length < 3) return result;
// Crucial: JavaScript default sort converts numbers to strings!
// Always pass the numeric comparator: (a, b) => a - b
nums.sort((a, b) => a - b);
const len = nums.length;
for (let i = 0; i < len - 2; i++) {
// Optimization: Smallest number > 0 means impossible to sum to 0
if (nums[i] > 0) break;
// Skip duplicate values for the first element of the triplet
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = len - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
// Skip consecutive duplicates for left pointer
while (left < right && nums[left] === nums[left + 1]) left++;
// Skip consecutive duplicates for right pointer
while (left < right && nums[right] === nums[right - 1]) right--;
// Advance past the processed unique values
left++;
right--;
} else if (sum < 0) {
// Sum too small, move left pointer rightward to increase value
left++;
} else {
// Sum too large, move right pointer leftward to decrease value
right--;
}
}
}
return result;
}3. Pattern 2: The Sliding Window (Fixed vs. Dynamic)
If Two Pointers is two people walking towards each other, the Sliding Window is an accordion expanding and contracting across an array or string.
When to Use It:
- Longest/shortest contiguous subarray or substring matching a condition.
- Fixed-size rolling calculations (e.g., maximum sum of
kconsecutive elements). - Frequency matching (anagrams, permutations).
The Dynamic Sliding Window State Machine:
Every dynamic window follows this universal loop invariant:
- Expand: Increment
rightpointer, incorporatearray[right]into your window state. - Evaluate: Check if the window is now "valid" (or "invalid").
- Contract: While the condition demands it, increment
leftpointer, striparray[left]out of your window state, and update the optimal answer.
Challenge 2: Minimum Window Substring (LeetCode 76 - Hard)
This is one of the highest-yield interview problems in history. It tests string iteration, map frequency reconciliation, and window-boundary discipline.
Problem: Given two strings
sandtof lengthsmandnrespectively, return the minimum window substring ofssuch that every character int(including duplicates) is included in the window. If there is no such substring, return"".
The Architecture of the Solution:
Instead of constantly comparing two hash maps of character counts (which introduces a hidden O(26) or O(128) check on every step), we maintain:
needMap: Target frequencies of characters int.required: Number of unique characters intwhose frequencies must be satisfied.formed: How many unique characters currently meet the frequency requirement in the current window.
Step-by-Step Execution:
- Move
right. If the character is inneedMap, record its presence inwindowMap. - When
windowMap[char] === needMap[char], incrementformed++. - Once
formed === required, the window contains all needed characters! - Now squeeze from the left:
- Check if the current window length is the smallest seen so far. If so, record
start = leftandminLen = right - left + 1. - Eject
s[left]fromwindowMap. - If
windowMap[s[left]] < needMap[s[left]], our window is no longer complete! Decrementformed--. - Increment
left.
- Check if the current window length is the smallest seen so far. If so, record
- Repeat until
rightreaches the end of strings.
Target: "ABC" -> need: {A:1, B:1, C:1}, required: 3
s = " A D O B E C O D E B A N C "
[-----------------] -> Window has A, B, C! formed == 3
[--------------] -> Shrink 'A': formed drops to 2.
[-----] -> New minimum window: "BANC"The Code:
/**
* Time Complexity: O(|s| + |t|) - each character in s is visited at most twice (left and right)
* Space Complexity: O(|s| + |t|) - frequency maps for ASCII / UTF-16 characters
* @param {string} s
* @param {string} t
* @return {string}
*/
function minWindow(s, t) {
if (!s || !t || s.length < t.length) return "";
// 1. Build frequency profile for t
const targetMap = new Map();
for (let i = 0; i < t.length; i++) {
const char = t[i];
targetMap.set(char, (targetMap.get(char) || 0) + 1);
}
const required = targetMap.size; // Number of unique characters that must match
const windowMap = new Map();
let formed = 0; // Number of unique characters currently matched with required frequency
let left = 0;
let right = 0;
// Tracking minimal substring window: [windowLength, startIndex, endIndex]
let minLen = Infinity;
let minStart = 0;
while (right < s.length) {
const char = s[right];
windowMap.set(char, (windowMap.get(char) || 0) + 1);
// If frequency of current character matches target frequency, increment formed
if (targetMap.has(char) && windowMap.get(char) === targetMap.get(char)) {
formed++;
}
// Try and contract the window till the point where it ceases to be 'desirable'
while (left <= right && formed === required) {
const currentLen = right - left + 1;
if (currentLen < minLen) {
minLen = currentLen;
minStart = left;
}
const leftChar = s[left];
// Reduce count of character leaving the window
windowMap.set(leftChar, windowMap.get(leftChar) - 1);
if (targetMap.has(leftChar) && windowMap.get(leftChar) < targetMap.get(leftChar)) {
formed--;
}
left++; // Contract window from the left
}
right++; // Expand window to the right
}
return minLen === Infinity ? "" : s.substring(minStart, minStart + minLen);
}4. Pattern 3: The Peak-and-Valley Two Pointer Invariant
Some problems look like dynamic programming or stack problems until you realize that water or area bounds are strictly determined by the shorter of two extremes.
Challenge 3: Trapping Rain Water (LeetCode 42 - Hard / The Toptal Final Round Filter)
Problem: Given
nnon-negative integers representing an elevation map where the width of each bar is1, compute how much water it can trap after raining.
Elevation Map:
#
# ## #
_#_##_######
[0,1,0,2,1,0,1,3,2,1,2,1] -> Water Trapped = 6The Intuition:
At any index i, how much water sits on top of bar i?
water[i] = max(0, min(max_height_to_left, max_height_to_right) - height[i])You could precompute prefix max and suffix max arrays. That takes O(n) time and O(n) space.
Can we do it in O(1) auxiliary space?
Yes. We maintain two pointers: left = 0 and right = height.length - 1, along with leftMax and rightMax.
Here is the key deduction:
- If
height[left] < height[right]:- We know for certain that there is a wall on the right that is at least as tall as
height[left]. - Therefore, the water trapped at
leftis purely dictated byleftMax. - If
height[left] >= leftMax, updateleftMax = height[left]. - Else, add
leftMax - height[left]to total water. - Move
left++.
- We know for certain that there is a wall on the right that is at least as tall as
- Otherwise (
height[left] >= height[right]):- The bottleneck is guaranteed to be on the right.
- If
height[right] >= rightMax, updaterightMax = height[right]. - Else, add
rightMax - height[right]to total water. - Move
right--.
The Code:
/**
* Time Complexity: O(n) - Single pass through the array
* Space Complexity: O(1) - Constant auxiliary memory
* @param {number[]} height
* @return {number}
*/
function trap(height) {
if (!height || height.length < 3) return 0;
let left = 0;
let right = height.length - 1;
let leftMax = 0;
let rightMax = 0;
let totalWater = 0;
while (left < right) {
if (height[left] < height[right]) {
// The right side is guaranteed to be taller than height[left].
// Thus, leftMax is our only bottleneck.
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
totalWater += leftMax - height[left];
}
left++;
} else {
// The left side is guaranteed to be taller than or equal to height[right].
// Thus, rightMax is our only bottleneck.
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
totalWater += rightMax - height[right];
}
right--;
}
}
return totalWater;
}5. The Toptal Execution Playbook: Writing Code Under Pressure
Toptal testing environments (usually Codility) are unforgiving:
- No interactive debugger. You get
console.logand standard out. - Hidden test cases. Your score isn't just correctness; it's Performance + Correctness. If your solution is
O(n^2)when anO(n)exists, you will score 50% and fail the round. - Extreme inputs: Arrays of length 100,000, all identical numbers, arrays sorted in reverse order, negative numbers, empty arrays, integer overflow edge cases.
The 4-Step Discipline to Follow on Every Linear Problem:
- Never write
shift()orunshift()inside a loop. Period. - Check empty or 1-element arrays before anything else.
if (!arr || arr.length < 2) return ...; - Beware of JavaScript's
sort()gotcha.[10, 2, 5].sort(); // Yields [10, 2, 5] because "10" < "2" lexicographically! [10, 2, 5].sort((a, b) => a - b); // Correct: [2, 5, 10] - Name your pointers descriptively. Avoid
p1,p2,x,y. Useleft,right,slow,fast,windowStart,windowEnd. It keeps you from getting confused when edge cases crop up at minute 42 of your 60-minute interview.
6. Your Practice Gauntlet (Before Part 2)
Do not move to Part 2 until you can write clean, bug-free JavaScript solutions to these without looking at the solutions:
- LeetCode 11 (Medium): Container With Most Water (Warm-up for Two Pointers)
- LeetCode 167 (Medium): Two Sum II - Input Array Is Sorted (Pointers & bounds)
- LeetCode 3 (Medium): Longest Substring Without Repeating Characters (Sliding Window with a Set/Map)
- LeetCode 438 (Medium): Find All Anagrams in a String (Fixed-size Sliding Window)
- LeetCode 42 (Hard): Trapping Rain Water (Implement the
O(1)space two-pointer approach from memory)
What’s Coming Next in Part 2
In Part 2, we leave flat linear arrays behind and enter the memory stack:
- Linked Lists: Fast & Slow Pointers (Floyd’s Cycle Detection) and in-place reversal tricks.
- The Monotonic Stack: The secret technique behind solving "Next Greater Element", "Largest Rectangle in Histogram", and daily temperature spikes.
- Designing an LRU Cache in JavaScript: Why a simple
Maptechnically works, how interviewers test you on it, and how to build a doubly linked list + hash map from raw pointers.