Merge and Sort Two Arrays
Merging two arrays of numbers is the kind of task whose right answer depends entirely on whether the inputs are already sorted. This snippet shows three patterns: a simple concat-then-sort for small or unsorted inputs, the classic two-pointer linear merge for already-sorted inputs, and an in-place variant that fills a preallocated buffer for hot paths. Pick by input shape and size, not by habit.
1,107 views
15
// Default JS sort uses lexicographic order on strings, so always pass a comparator
// for numbers (otherwise [1, 10, 2] becomes [1, 10, 2], not [1, 2, 10]).
const mergeAndSort = (a, b) => [...a, ...b].sort((x, y) => x - y);
console.log(mergeAndSort([3, 1, 4], [5, 2, 6])); // [1, 2, 3, 4, 5, 6]
console.log(mergeAndSort([10, 1], [2, 20])); // [1, 2, 10, 20]This is the right default when the inputs are not already sorted, when the combined size is small (under a few thousand items), or when readability matters more than micro-optimization. Time complexity is O((n + m) log(n + m)) because the engine sorts the concatenated result. The mandatory comparator ((x, y) => x - y) is the single biggest gotcha: omit it and Array.prototype.sort coerces values to strings and sorts lexicographically, which produces wrong order for any number with two or more digits.
// Linear-time merge of two sorted arrays. This is the merge step of mergesort.
const mergeSorted = (a, b) => {
const out = [];
let i = 0;
let j = 0;
while (i < a.length && j < b.length) {
if (a[i] <= b[j]) out.push(a[i++]);
else out.push(b[j++]);
}
// Drain whichever side has leftovers.
while (i < a.length) out.push(a[i++]);
while (j < b.length) out.push(b[j++]);
return out;
};
console.log(mergeSorted([1, 4, 7], [2, 3, 8, 9])); // [1, 2, 3, 4, 7, 8, 9]
console.log(mergeSorted([], [1, 2, 3])); // [1, 2, 3]
console.log(mergeSorted([5], [])); // [5]When both inputs are already sorted, you can merge in O(n + m) linear time by walking two pointers and copying the smaller current value into the output. This is the canonical interview answer and it is also faster in practice than concat-then-sort once the combined size grows. The <= (not <) keeps the merge stable: equal values from a come before equal values from b, which matters when each item carries extra fields. Always test the empty-input cases; an off-by-one in the drain loops is the most common bug.
// Allocate the output once instead of relying on push() to grow.
// Useful when you do this in a tight loop or know the size in advance.
const mergeIntoBuffer = (a, b) => {
const out = new Array(a.length + b.length);
let i = 0;
let j = 0;
let k = 0;
while (i < a.length && j < b.length) {
out[k++] = a[i] <= b[j] ? a[i++] : b[j++];
}
while (i < a.length) out[k++] = a[i++];
while (j < b.length) out[k++] = b[j++];
return out;
};
const left = [1, 3, 5, 7, 9];
const right = [2, 4, 6, 8, 10];
console.log(mergeIntoBuffer(left, right));
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]Preallocating the result with new Array(a.length + b.length) and writing by index avoids the push resize cost on hot paths. The cost difference is small for one call and meaningful inside a tight loop, where v8 cannot always elide the dynamic growth. The structure is the same two-pointer merge, just with index-based writes. Use this version inside a hand-rolled mergesort or when you measure that push shows up in a profiler; for one-off calls in product code, prefer the readable variant in accordion 2.
