Arrays & Lists: Four Core Techniques That Solve Dozens of Problems
When you are starting out, every array problem feels brand new. You solve "remove duplicates," then you meet "move all zeros to the end," and it feels like a completely different puzzle — even though, underneath, it is the same idea wearing different clothes. The goal of this post is to fix exactly that. We are going to look at four small array problems we have already solved and, for each one, extract the reusable technique hiding inside it.
Because here is the secret about array interviews: there are not fifty different tricks. There are a handful, used over and over. Learn to name them, and suddenly a whole shelf of "new" problems becomes "oh, that is just the two-pointer merge again." Let us build that vocabulary, one technique at a time, in plain language.
Technique 1 — Compare Neighbours / Track the Last-Seen
The problem that teaches it: Remove duplicates from a sorted list.
The idea in one line: when a list is sorted, every duplicate sits right next to its twin — so you only ever need to compare each element with the one just before it.
This is the gentlest and most important beginner insight: sorted means duplicates are neighbours. You do not need a hash set or a nested loop to find repeats — you just walk once and ask, "is this the same as the last unique value I kept?"
The clean way to do it in place uses a "write pointer" — a slot that marks where the next unique element should go:
function removeDuplicates(arr) {
if (arr.length === 0) return 0;
let write = 1; // index where the next unique value will be placed
for (let read = 1; read < arr.length; read++) {
// compare with the last value we KEPT (at write - 1)
if (arr[read] !== arr[write - 1]) {
arr[write] = arr[read];
write++;
}
}
return write; // the new length (count of unique elements)
}
console.log(removeDuplicates([1, 1, 2, 2, 2, 3])); // 3 → arr starts with [1, 2, 3, ...]The read pointer scans everything; the write pointer only advances when we find something genuinely new. That "two pointers, one reading and one writing" shape is worth burning into memory — it is the backbone of every in-place array problem.
Similar problems to practice:
- Move all zeros to the end (in-place) — same write-pointer idea:
writeadvances only on non-zero values, then fill the rest with zeros. - Remove a given value in-place, return the new length — literally the same skeleton, just "keep it if it is not the target value."
Technique 2 — Adjacent-Pair Scan
The problem that teaches it: Maximum consecutive difference.
The idea in one line: many questions are really about each element and its immediate neighbour — so walk the array looking at pairs (arr[i-1], arr[i]).
If a problem talks about "consecutive," "adjacent," "next element," or "difference between neighbours," you almost never need anything fancy. One pass, comparing each pair, does it:
function maxConsecutiveDifference(arr) {
let maxDiff = 0;
for (let i = 1; i < arr.length; i++) {
maxDiff = Math.max(maxDiff, Math.abs(arr[i] - arr[i - 1]));
}
return maxDiff;
}
console.log(maxConsecutiveDifference([1, 5, 2, 9, 4])); // 7 (from 2 → 9)If you have seen Python, this is the beautiful zip(lst, lst[1:]) idiom — pairing the list with a copy of itself shifted by one, so you iterate over neighbour pairs directly. In JavaScript we just index i and i - 1; same idea, different spelling.
The mental trigger: "do I only ever need to look one step back (or forward)?" If yes, it is a single O(n) pass, no cleverness required.
Similar problems to practice:
- Find the max/min difference between any two elements — a small twist: instead of adjacent pairs, track the running minimum as you scan and compare each element against it. (This is the "best time to buy/sell stock" skeleton.)
Technique 3 — Two-Pointer Merge
The problem that teaches it: Merge two sorted lists.
The idea in one line: to combine two already-sorted lists, walk both at once with a pointer in each, and always take the smaller head.
This is one of the most useful patterns in all of computing — it is literally the "merge" step of merge sort. Because both inputs are sorted, you never have to search: the next smallest element of the combined result is always at the front of one of the two lists. So you just compare the two fronts and take the winner:
function mergeTwoSorted(a, b) {
let result = [];
let i = 0, j = 0;
// walk both lists, always taking the smaller front element
while (i < a.length && j < b.length) {
if (a[i] <= b[j]) result.push(a[i++]);
else result.push(b[j++]);
}
// one list may still have leftovers — append them (already sorted)
while (i < a.length) result.push(a[i++]);
while (j < b.length) result.push(b[j++]);
return result;
}
console.log(mergeTwoSorted([1, 3, 5], [2, 4, 6])); // [1, 2, 3, 4, 5, 6]The key realisation for a beginner: sorted inputs mean you never look backwards. Each pointer only ever moves forward, so the whole merge is O(n + m) — a single sweep.
Similar problems to practice:
- Merge intervals — sort the intervals by start, then sweep once, merging each interval into the previous one if they overlap. Same "sort then one-pass sweep" spirit.
- Merge K sorted lists — the natural generalisation. Instead of comparing 2 fronts, you compare
kfronts, which is exactly what a min-heap (priority queue) is for. Recognising that "two-pointer merge scaled up = heap" is a lovely aha moment.
Technique 4 — The Three-Reversal Rotation Trick
The problem that teaches it: Rotate a list by k positions.
The idea in one line: rotating an array is just three reversals — and it needs no extra array.
This one feels like a magic trick the first time. To rotate [1, 2, 3, 4, 5] right by k = 2 into [4, 5, 1, 2, 3], you do this:
- Reverse the whole array →
[5, 4, 3, 2, 1] - Reverse the first k elements →
[4, 5, 3, 2, 1] - Reverse the rest →
[4, 5, 1, 2, 3]✅
Why does this work? Rotating by k means the last k elements should jump to the front, keeping their internal order. Reversing the whole array brings those last k to the front — but backwards. So we reverse each of the two chunks again to fix their internal order. Two wrongs (reversals) make a right.
function rotate(arr, k) {
let n = arr.length;
if (n === 0) return arr;
k %= n; // if k is bigger than n, only the remainder matters
reverse(arr, 0, n - 1); // reverse everything
reverse(arr, 0, k - 1); // reverse the first k
reverse(arr, k, n - 1); // reverse the rest
return arr;
}
function reverse(arr, left, right) {
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]]; // swap
left++;
right--;
}
}
console.log(rotate([1, 2, 3, 4, 5], 2)); // [4, 5, 1, 2, 3]Do not skip that k %= n line — it is the detail interviewers check for. If someone asks to rotate a 5-element array by k = 7, that is the same as rotating by 2 (because rotating by 5 lands you back where you started). Taking k modulo n handles it and prevents out-of-bounds bugs. This small guard is exactly the kind of edge case that separates a careful answer from a buggy one.
Similar problems to practice:
- Rotate a matrix 90° — the 2D cousin of this trick. The clean solution is transpose (flip across the diagonal) followed by reversing each row. Same spirit as the three-reversal idea: achieve a big rearrangement through a couple of simple, reversible flips instead of copying into a new grid.
The Recognition Table
Here is the whole post compressed into a lookup you can scan when a new array problem lands in front of you:
| If the problem is about… | Reach for… | Because |
|---|---|---|
| removing/keeping elements in place | write-pointer (Technique 1) | one pointer reads, one writes |
| neighbours / consecutive / differences | adjacent-pair scan (Technique 2) | you only look one step away |
| combining sorted data | two-pointer merge (Technique 3) | sorted fronts never need searching |
| shifting / rotating / rearranging | reversal tricks (Technique 4) | flips rearrange without extra space |
And the practice problems, mapped to the technique each one stretches:
| Practice problem | Technique it extends |
|---|---|
| Move all zeros to the end | Technique 1 (write pointer) |
| Remove a given value in-place | Technique 1 (write pointer) |
| Max/min difference between two elements | Technique 2 (scan + running min) |
| Merge intervals | Technique 3 (sort + one-pass merge) |
| Merge K sorted lists | Technique 3 (scaled up with a heap) |
| Rotate a matrix 90° | Technique 4 (transpose + reverse) |
Final Thoughts
Notice what just happened. Four small problems, but they did not teach us four problems — they taught us four techniques: the write pointer, the adjacent-pair scan, the two-pointer merge, and the reversal trick. Every one of the practice problems above is just one of these four ideas with a small twist.
That is the real skill in array questions. When a new one appears, do not ask "have I seen this exact problem?" Ask instead:
"Which of my handful of techniques does the shape of this problem match?"
Answer that, and you are no longer memorising solutions — you are recognising patterns. And pattern recognition, not memorisation, is what carries you through interviews and real engineering alike.
Happy coding, and all the best for your interviews!