Two pointers
Picture two lines, each at its own service window. In each line people stand in ticket-number order, from smaller to larger. One window closes, and the two lines must become one, also in number order.
There is no need to rearrange everyone from scratch. It is enough to look at the first person in each line and let through the one with the smaller number. Each person goes through once, and the new line comes out in order right away.
In a program such lines are two sorted arrays. Joining them into one that is sorted too is called a merge. The shortest way is to join the arrays and sort the result with the sort method:
const a = [1, 3, 6, 8];
const b = [2, 3, 8];
const all = [...a, ...b].sort((x, y) => x - y);
console.log(all.join(" ")); // 1 2 3 3 6 8 8Let's go through the line with sort:
[...a, ...b]spreads both arrays into one new array:[1, 3, 6, 8, 2, 3, 8].sort((x, y) => x - y)puts it in ascending order.all.join(" ")prints1 2 3 3 6 8 8.
The result is correct, but sort is built for numbers in any order. The language does not promise that the method makes use of a and b being sorted already. Without that knowledge, sorting a long array makes noticeably more comparisons than there are numbers in it, while a merge needs at most one comparison per number.
Interviewers often ask to merge sorted arrays. The answer "join and sort" usually does not satisfy them: they expect a solution in one pass over both arrays.
Two indexes
The two pointers technique walks through data with two indexes, each of which moves on its own. A pointer here is an ordinary variable holding an element's position. In the lines, it is the finger you keep on the first person who has not gone through yet.
A merge step
In a merge, index i walks through a and index j walks through b. At each step:
a[i]andb[j]are compared.- The smaller of the two numbers goes into the result.
- Only the index of that number moves.
On a tie either of the two can be taken, but here a[i] is taken. For the arrays from the example above, the steps are these:
| Comparison | Into the result | Moves |
|---|---|---|
1 and 2 | 1 | i |
3 and 2 | 2 | j |
3 and 3 | 3 | i |
6 and 3 | 3 | j |
6 and 8 | 6 | i |
8 and 8 | 8 | i |
The tail of an array
After the sixth step, array a has run out, and 8 is left in b. Such a tail is sorted already and no smaller than anything in the result. So it is appended whole to the result array out: out.push(...b.slice(j)).
Which array runs out first depends on the data. So after the loop both tails are appended, a.slice(i) and b.slice(j): the empty one adds nothing.
This is where the lines analogy ends. People leave a line, but the numbers in a and b stay where they are: only the indexes move, and the merge does not change the arrays themselves.
Each step sends one number to the result, so merging arrays of length n and m takes at most n + m steps.
Common elements
The same pass finds the numbers present in both sorted arrays:
const common = [];
let i = 0;
let j = 0;
while (i < a.length && j < b.length) {
if (a[i] < b[j]) {
i++;
} else if (a[i] > b[j]) {
j++;
} else {
common.push(a[i]);
i++;
j++;
}
}
console.log(common.join(" ")); // 3 8Let's see why the loop is built this way:
- The loop runs while both indexes are inside their arrays.
- The smaller number will not turn up later in the other array: only larger numbers stand there. So the index of the smaller number moves.
- Of two equal numbers, one goes into the result, and both indexes move.
Press "Next step" and watch i and j: at each step the index of the smaller number moves, and on a tie both do.
1const a = [1, 3, 6, 8];2const b = [2, 3, 8];3const common = [];4let i = 0;5let j = 0;6while (i < a.length && j < b.length) {7 if (a[i] < b[j]) {8 i++;9 } else if (a[i] > b[j]) {10 j++;11 } else {12 common.push(a[i]);13 i++;14 j++;15 }16}17console.log(common.join(" "));
Both indexes stand on the first elements, and the list of common numbers is empty.
For these arrays the loop takes 5 steps, while comparing every number of a with every number of b would take 12.
Common mistake
The tail of an array is passed to push without the three dots:
const out = [1, 2, 3];
const rest = [7, 9];
out.push(rest); // ✗
console.log(out.length);There is no error, but the program prints 4, not 5. The push method got one argument, an array, and put it into out whole, as one element. Spread syntax splits the tail into separate numbers: out.push(...rest).
Key points
- Two sorted arrays are merged in one pass with two indexes, without
sort. - At each step the smaller number goes into the result, and only its index moves.
- After the loop both tails are appended:
a.slice(i)andb.slice(j). - An array is passed to
pushwith three dots, or it lands in the result as one element.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.