Merge Sorted Array problem in DSA: Brute Force, Merge Copy, and the Three-Pointer Trick
-
Last Updated: July 20, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Learn the Merge Sorted Array problem in DSA the beginner-friendly way. We compare brute force, a copy merge, and the in-place three-pointer trick, with dry runs for all three and ten interview questions at the end. As a developer I will be using Java code but pseudo-codes, explanation and dry runs are language neutral and you can understand the concept easily. So don’t skip it because language is just a syntax.
The Merge Sorted Array problem in DSA is one of those questions that looks easy, then trips you up on the details. You get two sorted arrays and you have to blend them into one. The twist is where the answer must live, and that small rule changes everything.
Here is the task in plain words. Somebody hands you two arrays that are already sorted. The first one carries extra empty space at the end, just enough to hold the second array. Your job is to pour the second array into the first and keep the whole thing sorted.
That spare tail is not an accident. It is a hint, and the best solution treats it as free workspace rather than as clutter to sort around.
We go slow here. First we try the blunt way, which is to dump everything in and sort. Then we merge into a fresh copy like a careful librarian. Finally we reach the three-pointer trick that fills the array from the back and needs no extra space at all.
Here is the road map for the rest of the guide.
Let us pin down the rules first. This problem hides a few traps, and a clear statement keeps you safe.
Every version of this question comes down to the same five rules.
Worth knowing about the original version of this question: both m and n stay under a few hundred, and values range across roughly a billion in either direction. So the arrays are small. That means the interviewer is not testing raw speed here, they are testing whether you spot the free space.
Take the first array as [1, 2, 3, 0, 0, 0] with m equal to 3, and the second as [2, 5, 6] with n equal to 3. After merging, the first array should read [1, 2, 2, 3, 5, 6].
Notice the two 2s sitting side by side. One came from each array, and duplicates are perfectly legal. Nothing gets removed here, so the final length is always m plus n.
Count the slots as a sanity check. Three real values plus three more equals six, which is exactly the length of the first array. That is never a coincidence, and it is the promise the spare tail is making.
Those trailing zeros catch out almost everybody the first time. They look like data, and they are not.
Read them as empty parking bays. A zero in the tail means “nothing here yet”, so sorting it as a real value would drag a fake 0 into the middle of your answer. On our example that mistake produces [0, 0, 0, 1, 2, 3] plus the second array, which is nonsense.
This is why the count m exists as a separate argument. Without it you could not tell a real 0 in the data from a placeholder 0 in the tail. Trust m, never the zeros.
| 💡 Interview Insight Interviewers love the in-place twist here. Say you would build a brand new array, and they will nudge you toward the empty tail of the first array instead. Naming that free space early shows you actually read the constraints. |
Before the code, let us name the ideas that carry this problem. Each one is small, and you will reuse them in many array questions.
Both arrays arrive already sorted. That is a gift, and throwing it away is the main sin of the brute force.
When two lists are sorted, you can walk them side by side and always know which candidate is next. Compare the two front values, take the smaller, and step that side forward. Repeat.
Think of two queues merging into one door. You never look further back than the person at the head of each queue, because nobody behind them can be shorter than they are.
The empty room sits at the end of the first array. So the safest place to write is the back, not the front.
Flip the comparison around and it works just as well. Instead of repeatedly taking the smallest remaining value and placing it at the front, take the largest remaining value and place it at the back. Both orders produce the same sorted result.
That single change is the heart of the best solution. Same merge, opposite direction, and suddenly no extra array is needed.
Now the detail that makes all of this click. Suppose you merge from the front, writing directly into the first array with no copy.
Take [1, 2, 3, 0, 0, 0] and [2, 5, 6] again. The very first comparison picks 1, which happily lands in slot 0. The second comparison picks a 2, and here comes the problem: writing it into slot 1 destroys the 2 that was already living there, and we still needed to read it.
So a front-to-back merge must protect the originals first, which is exactly why the copy approach exists. Every write lands on a slot whose value has not been used yet.
Filling from the back has no such problem, and here is the reason.
The first idea most people reach for is blunt but honest. Copy every value from the second array into the empty tail of the first. Then sort the whole thing and call it done.
for j from 0 to n-1:
nums1[m + j] = nums2[j] // fill the empty tail
sort(nums1) // let the library sort itLet us read that one line at a time, in plain words.
So the plan is simple. First we pour the second array into the empty slots, then we let the sort clean up. It is short, and it is hard to get wrong.
import java.util.Arrays;
public class MergeSortedArrayBrute {
public static void merge(int[] nums1, int m,
int[] nums2, int n) {
for (int j = 0; j < n; j++) {
nums1[m + j] = nums2[j];
}
Arrays.sort(nums1);
}
public static void main(String[] args) {
int[] nums1 = { 1, 2, 3, 0, 0, 0 };
int[] nums2 = { 2, 5, 6 };
merge(nums1, 3, nums2, 3);
System.out.println(Arrays.toString(nums1));
// Output: [1, 2, 2, 3, 5, 6]
}
}Let us read the logic slowly. Each part does one small job.
This version is easy to read, and it always gives the correct answer. That is its strength. Its weakness is that it throws away the fact that both inputs arrived already sorted.
Let us trace it with [1, 2, 3, 0, 0, 0], m equal to 3, and [2, 5, 6], n equal to 3. The empty slots sit at positions 3, 4, and 5. We copy first, then we sort.
| Step | Action | First array after step |
|---|---|---|
| 1 | Copy 2 into slot 3 | [1, 2, 3, 2, 0, 0] |
| 2 | Copy 5 into slot 4 | [1, 2, 3, 2, 5, 0] |
| 3 | Copy 6 into slot 5 | [1, 2, 3, 2, 5, 6] |
| 4 | Loop ends, the sort reorders everything | [1, 2, 2, 3, 5, 6] |
Let us narrate the trace, so nothing feels like magic.
[1, 2, 3, 2, 5, 6].So the stray 2 is the whole reason we need the sort. Copying alone does not keep things ordered. That sort works, yet it redoes effort, because both inputs were already sorted before we started.
The copy loop takes O(n) time. Sorting takes O((m + n) log (m + n)) time, which dominates everything else, so that is the overall cost.
Space stays at O(1) beyond the array itself, because the sort runs in place. Memory is not the problem here.
On small inputs this is genuinely fine, and nobody will notice the difference. On larger ones that log factor is pure waste, since the sorted order we needed was handed to us for free.
Here is a smarter middle ground. Both arrays are sorted, so let us actually use that. We keep a copy of the first m values, then merge that copy with the second array, writing results into the first array from the front.
copy = first m values of nums1
i = 0 // read pointer in copy
j = 0 // read pointer in nums2
k = 0 // write pointer in nums1
while i < m and j < n:
if copy[i] <= nums2[j]:
nums1[k] = copy[i]; k = k + 1; i = i + 1
else:
nums1[k] = nums2[j]; k = k + 1; j = j + 1
copy any leftover from copy or nums2This one has more moving parts, so let us unpack it.
Here is the key idea. Since both lists are sorted, the smaller front value is always the next one to place. So one clean sweep is enough.
import java.util.Arrays;
public class MergeSortedArrayCopy {
public static void merge(int[] nums1, int m,
int[] nums2, int n) {
int[] copy = Arrays.copyOf(nums1, m);
int i = 0, j = 0, k = 0;
while (i < m && j < n) {
if (copy[i] <= nums2[j]) {
nums1[k++] = copy[i++];
} else {
nums1[k++] = nums2[j++];
}
}
while (i < m) nums1[k++] = copy[i++];
while (j < n) nums1[k++] = nums2[j++];
}
public static void main(String[] args) {
int[] nums1 = { 1, 2, 3, 0, 0, 0 };
int[] nums2 = { 2, 5, 6 };
merge(nums1, 3, nums2, 3);
System.out.println(Arrays.toString(nums1));
// Output: [1, 2, 2, 3, 5, 6]
}
}Now let us map each part to what it actually does.
That stability detail is worth a sentence. Taking from the backup on a tie means values originally in the first array stay ahead of equal values from the second. Nobody checks this on a plain integer array, though it matters enormously the moment you merge records with keys.
Let us trace with a backup of [1, 2, 3] and a second array of [2, 5, 6]. All three pointers start at 0, and we write from the front. An underscore marks a slot we have not written yet.
| Step | Backup vs second array | Smaller wins | Merged so far |
|---|---|---|---|
| 1 | 1 vs 2 | 1 wins, write 1 | [1, _, _, _, _, _] |
| 2 | 2 vs 2 | Tie, take the backup, write 2 | [1, 2, _, _, _, _] |
| 3 | 3 vs 2 | 2 wins, write 2 | [1, 2, 2, _, _, _] |
| 4 | 3 vs 5 | 3 wins, write 3 | [1, 2, 2, 3, _, _] |
| 5 | Backup empty, drain the rest | Write 5, then 6 | [1, 2, 2, 3, 5, 6] |
Let us narrate the pointer dance, step by step.
Once one side empties, no more comparing is needed. Whatever is left is already sorted, so it just gets copied across in order.
Every value gets read once and written once, so the time cost is O(m + n). That beats the brute force by dropping the log factor entirely.
Space climbs to O(m), because of the backup array. Notice it is O(m) and not O(m + n), since we only ever copy the real values from the first array.
So this approach fixes the speed problem and introduces a memory problem. The third approach fixes both at once.
Now for the version interviewers really want. The idea is to fill the first array from the back, using its own empty tail as the workspace. No copy array, no re-sort, just one smart pass.
Think of stacking boxes into a truck from the rear. You place the biggest box last, right at the very back, then work forward. Because the empty space sits at the end, the back is always safe to write.
i = m - 1 // last real value in nums1
j = n - 1 // last value in nums2
k = m + n - 1 // last slot in nums1
while j >= 0:
if i >= 0 and nums1[i] > nums2[j]:
nums1[k] = nums1[i]; k = k - 1; i = i - 1
else:
nums1[k] = nums2[j]; k = k - 1; j = j - 1This is the clever one, so let us read every line carefully.
The trick is direction. We place the biggest remaining value at the back first, then work toward the front. Since the free space sits at the back, the write position never lands on a value we still need.
import java.util.Arrays;
public class MergeSortedArrayThreePointer {
public static void merge(int[] nums1, int m,
int[] nums2, int n) {
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while (j >= 0) {
if (i >= 0 && nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
}
public static void main(String[] args) {
int[] nums1 = { 1, 2, 3, 0, 0, 0 };
int[] nums2 = { 2, 5, 6 };
merge(nums1, 3, nums2, 3);
System.out.println(Arrays.toString(nums1));
// Output: [1, 2, 2, 3, 5, 6]
}
}The code mirrors the pseudocode closely. Here is what each part does as it runs.
Why watch the second reader and not the write position? Once the second array empties, whatever remains in the first array is already sorted and already sitting in the correct front slots. So there is nothing left to do, and we stop early.
And why the validity guard? Sometimes the first array runs out of real values before the second does. The guard then fails, we fall into the other branch, and the rest of the second array pours into the front slots. That one small check handles the tricky edge cases for free, and section 7.2 traces exactly that case.
| 💡 Interview Insight A classic follow-up asks why we loop on the second array rather than on the write position. The reason is neat: if the second array empties first, every remaining value in the first array is already sitting in its correct spot. So we can stop early and skip pointless writes. |
Each step places exactly one value, and there are m plus n values to place at most. So the time cost is O(m + n), matching the copy merge.
Space never grows beyond three index variables, which puts us at O(1). No backup array, no allocation, nothing to garbage collect.
That combination is why this is the expected answer. It ties the copy merge on speed and ties the brute force on memory, so it loses to neither.
Watching the logic run by hand makes it stick. Let us trace [1, 2, 3, 0, 0, 0] with m equal to 3, and [2, 5, 6] with n equal to 3. The readers start at slots 2 and 2, and the writer starts at slot 5. Watch all three slide left.
| Step | Read positions | Comparison | First array after step |
|---|---|---|---|
| 1 | first 2, second 2, write 5 | 3 vs 6, the 6 is bigger | [1, 2, 3, 0, 0, 6] |
| 2 | first 2, second 1, write 4 | 3 vs 5, the 5 is bigger | [1, 2, 3, 0, 5, 6] |
| 3 | first 2, second 0, write 3 | 3 vs 2, the 3 is bigger | [1, 2, 3, 3, 5, 6] |
| 4 | first 1, second 0, write 2 | 2 vs 2, tie, take the second | [1, 2, 2, 3, 5, 6] |
| 5 | second reader below zero | Loop ends, nothing left to place | [1, 2, 2, 3, 5, 6] |
Now let us walk through each turn slowly, following where every pointer moves.
Look at step five closely. The values 1 and 2 at the front never moved, because they were already in their correct spots. Stopping early is not laziness, it is the algorithm recognising that the remaining work is already done.

The sketch above shows the same trace. The two arrays sit on top, and each step shows the bigger value dropping into the back slot. Many people find this picture easier than the table, so use whichever clicks for you.
Now the case that breaks careless code. Take a first array of [0, 0, 0] with m equal to 0, and a second array of [2, 5, 6] with n equal to 3.
Here the first array holds no real values at all, so its reader starts at minus 1. It is invalid before the loop even begins.
| Step | First reader valid? | Action | First array after step |
|---|---|---|---|
| 1 | No, it is at -1 | Take 6 from the second array | [0, 0, 6] |
| 2 | No | Take 5 from the second array | [0, 5, 6] |
| 3 | No | Take 2 from the second array | [2, 5, 6] |
The validity guard fails every single time, so every step falls into the other branch and simply copies the second array across in reverse. Result: a correct answer with no special case written anywhere.
Drop that guard and step 1 would read the first array at position minus 1, which crashes immediately. This is the single most common bug in the three-pointer version, and it only shows up on inputs people forget to test.
All three approaches give the correct answer. They simply charge different prices for it.
| Point | Dump and Sort | Merge Into a Copy | Three Pointer |
|---|---|---|---|
| Time | O((m+n) log(m+n)) | O(m + n) | O(m + n) |
| Extra space | O(1) | O(m) | O(1) |
| Uses the sorted inputs | No | Yes | Yes |
| Direction of writing | Not applicable | Front to back | Back to front |
| Can stop early | No | No | Yes |
| Stable on ties | Not guaranteed | Yes | Order still correct |
| Good for interviews | As a starting point | As the middle step | As the final answer |
Read the space row against the time row. Dump and sort is cheap on memory and wasteful on time. The copy merge flips that exactly. Only the three-pointer version refuses to pay either price.
Context decides, and here is the short guide.
In an interview, walk that exact path. Mention dump and sort to show you understand the problem, bring up the copy merge to show you can use the sorted order, then reach for the three-pointer trick to remove the extra space. That climb from blunt to elegant is what interviewers grade you on.
| 💡 Interview Insight If the interviewer says memory is tight, the three-pointer trick is your answer. It uses no extra array and still runs in linear time. Saying that trade-off out loud signals real judgment, not just memorised Big-O. |
Solve the base problem quickly and a follow-up usually arrives. These two show up most often, and both reuse the merge you already know.
Take away the free tail and the problem changes shape. Two ordinary sorted arrays arrive, neither with room to grow, and you must produce a third array holding everything.
Without spare space, an allocation is unavoidable. The good news is that this version is simpler, because writing into a brand new array can never destroy an input.
public class MergeTwoSorted {
public static int[] mergeTwo(int[] a, int[] b) {
int[] out = new int[a.length + b.length];
int i = 0, j = 0, k = 0;
while (i < a.length && j < b.length) {
if (a[i] <= b[j]) {
out[k++] = a[i++];
} else {
out[k++] = b[j++];
}
}
while (i < a.length) out[k++] = a[i++];
while (j < b.length) out[k++] = b[j++];
return out;
}
public static void main(String[] args) {
int[] a = { 1, 4, 7 };
int[] b = { 2, 3, 9 };
System.out.println(java.util.Arrays.toString(mergeTwo(a, b)));
// Output: [1, 2, 3, 4, 7, 9]
}
}Look closely and you may recognise this. It is exactly the merge step of merge sort, which is why learning it here pays off twice.
Cost is O(m + n) time and O(m + n) space, and that space is genuinely required, since the output has to live somewhere. Mentioning the merge sort connection out loud is an easy way to show depth.
A natural escalation asks for k sorted arrays instead of two. The obvious move is to fold them together one at a time, merging the running result with the next array.
That works, and it is worth knowing why it disappoints. The running result keeps growing, so early values get copied again on every single merge.
So the answer depends on how large k gets. With three or four arrays, folding is perfectly sensible and much easier to read. With hundreds, the repeated copying dominates and a heap earns its keep.
A handful of small traps catch beginners on this problem. Read them once and you will dodge most of them.
An empty second array needs no special handling either. The loop simply never runs, and the first array is already the correct answer.
Let us tie everything into one small program. Two sorted score lists arrive from different sources, and we merge them into one ranked list with a short report.
import java.util.Arrays;
public class ScoreMerger {
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while (j >= 0) {
if (i >= 0 && nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
}
public static void report(int[] nums1, int m, int[] nums2, int n) {
int[] before = Arrays.copyOf(nums1, m);
merge(nums1, m, nums2, n);
System.out.println(Arrays.toString(before) + " + " + Arrays.toString(nums2)
+ " = " + Arrays.toString(nums1) + " (" + (m + n) + " scores)");
}
public static void main(String[] args) {
report(new int[] { 1, 2, 3, 0, 0, 0 }, 3, new int[] { 2, 5, 6 }, 3);
report(new int[] { 0, 0, 0 }, 0, new int[] { 2, 5, 6 }, 3);
report(new int[] { 4, 8 }, 2, new int[] {}, 0);
}
}[1, 2, 3] + [2, 5, 6] = [1, 2, 2, 3, 5, 6] (6 scores) [] + [2, 5, 6] = [2, 5, 6] (3 scores) [4, 8] + [] = [4, 8] (2 scores)
Three inputs, three different shapes of answer. That is the point of the exercise.
Notice the snapshot taken before merging. Since the merge overwrites in place, printing the original afterwards would be impossible without it. That is the practical cost of an in-place algorithm, and it is worth being aware of.
Notice too how little changed from the bare three-pointer version. The merge is identical, character for character. Everything else exists purely to make the result readable.
A: You get two arrays sorted in non-decreasing order. The first has spare room at the end, exactly big enough to hold the second. Your job is to merge the second into the first, in place, so the first ends up fully sorted.
A: The three-pointer trick wins. Start both readers at the last real values and the writer at the very last slot, then repeatedly place the larger value at the back. That gives O(m + n) time and O(1) extra space.
A: The free space sits at the end, so writing there never lands on a value still waiting to be read. Merging from the front would overwrite live data on the second write, unless you back up the originals first.
A: Nothing at all. They are placeholders marking empty room, not values. Only the count m tells you where the real data stops, which is exactly why that count is passed in separately.
A: Once the second array empties, everything left in the first is already sorted and already sitting in the right slots. Stopping there skips pointless writes and finishes early.
A: The first array can run out of real values before the second does, pushing its reader below zero. Checking validity before reading avoids an out-of-bounds crash and lets the remaining values pour in.
A: That works, and it costs O((m + n) log (m + n)) time. It also throws away the sorted order you were handed for free, which is precisely the thing the question is testing.
A: Very directly. Combining two sorted lists into one is the merge step of merge sort. Learn it here and you have learned half of that algorithm already.
A: Folding them one at a time costs roughly O(k times N), because early values get recopied repeatedly. A min-heap holding one candidate per array, or pairwise tournament merging, brings that down to O(N log k).
A: Try an empty second array, a first array with no real values, arrays where one is entirely larger than the other, and inputs full of duplicates. Those four catch nearly every bug here.
Let us wrap up what we covered. The Merge Sorted Array problem in DSA looks like a warm-up, and it is. Yet the fill-from-the-back idea inside it is a real skill you will reuse constantly.
We started with dump and sort. Pour the second array into the spare tail, sort everything, done. Correct and short, at a cost of O((m + n) log (m + n)) and a sorted order thrown away for nothing.
The copy merge came next and removed that waste. Back up the real values, walk both sorted lists side by side, and always take the smaller front value. Linear time, at the price of an O(m) backup array.
Three pointers finished the job by refusing both prices. Start at the back, place the larger value into the spare tail, and walk everything leftward. Linear time with constant space, and it stops early the moment the second array runs dry.
Along the way we handled the usual follow-ups. Without a spare tail you must allocate, and that merge is exactly merge sort’s merge step. With many arrays instead of two, a heap replaces repeated folding.
So take the pattern, not just the answer. When free space sits at one end, write towards it, and your writer can never trample a value your reader still needs. Once that habit feels natural, a whole family of in-place array problems opens up.