Merge Sorted Array in Java DSA: From an Extra Array to the Backward Sweep
-
Last Updated: July 29, 2026
-
By: javahandson
-
Series

Learn Merge Sorted Array in Java DSA with three approaches, full step-by-step dry runs, and the in-place backward two-pointer solution interviewers want.
Merge Sorted Array in Java is one of those problems that looks tiny but hides a neat trick. You get two sorted arrays. Your job is to blend them into one sorted array.
Here is the twist that trips people up. The first array already has empty room at its end, and you must do the merge inside that same array. No brand new array is allowed in the best solution.
We build the answer in three steps, like always. A simple version merges into a fresh array and copies back. A lazier version dumps everything together and sorts. The final one walks two pointers from the back and needs no extra space at all.
Every approach comes with a full, step-by-step dry run on the same seven numbers. Nothing is skipped, so you can follow exactly what each line does and what changes at every step.
Let us pin down the rules before touching code.
Take nums1 = [1, 3, 5, 7, 0, 0, 0] with m = 4, and nums2 = [2, 4, 6] with n = 3. The four real numbers are 1, 3, 5, 7. The three zeros are just parking space. After merging, nums1 should read [1, 2, 3, 4, 5, 6, 7].
The zeros matter. They are not part of the data, so treat them as free room you are allowed to overwrite.
This is the gift the problem hands you. Because both arrays climb in order, you never have to sort from scratch. You only have to pick the right next value at each step.
Two pointers usually crawl inward from both ends. Here we do something different. We fill nums1 from its last slot toward its first, and we compare the biggest leftover values.
Filling from the back is the key move. The empty room lives at the end of nums1, so writing there never stomps on a value we still need to read.
| 💡 Interview Insight A classic opener is “why merge backward instead of forward?” Say this: forward filling would overwrite nums1 values you have not read yet, but the free space sits at the back, so writing from the back is always safe. |
Build a fresh array of size m plus n. Walk both inputs from the front, always copying the smaller current value. Then copy the finished array back into nums1. It wastes memory, but it makes the merge logic crystal clear.
merged = new array of size m + n
i = 0 // pointer into nums1's real part
j = 0 // pointer into nums2
k = 0 // pointer into merged
while i < m and j < n:
if nums1[i] <= nums2[j]:
merged[k] = nums1[i]; i = i + 1
else:
merged[k] = nums2[j]; j = j + 1
k = k + 1
while i < m: merged[k++] = nums1[i++] // drain nums1
while j < n: merged[k++] = nums2[j++] // drain nums2
copy merged back into nums1The whole idea rests on three pointers. Read each one as a finger resting on an array. Here finger i walks the real part of nums1, finger j walks nums2, and finger k marks the next open slot in the new merged array.
The main while loop is the heart of the merge. It keeps running only while both inputs still have numbers left, because a merge needs two things to compare. At each turn we look at the two values under i and j, copy the smaller one into merged, and step that finger forward. Its k partner moves forward too, since one more slot is now filled.
Two drain loops then handle the leftover tail. When one input empties, the other may still hold larger values, and those are already sorted among themselves. So one drain loop simply pours the rest straight into merged, with no comparing needed.
A final copy step exists only because of the problem’s rule. Our answer sits in merged, but the problem wants it inside nums1, so we copy every value back over. That last copy is exactly the wasteful part we remove later.
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int[] merged = new int[m + n];
int i = 0, j = 0, k = 0;
while (i < m && j < n) {
if (nums1[i] <= nums2[j]) {
merged[k++] = nums1[i++];
} else {
merged[k++] = nums2[j++];
}
}
while (i < m) merged[k++] = nums1[i++];
while (j < n) merged[k++] = nums2[j++];
for (int t = 0; t < m + n; t++) nums1[t] = merged[t];
}Inputs: nums1 = [1, 3, 5, 7, 0, 0, 0], m = 4, and nums2 = [2, 4, 6], n = 3. We trace every write into merged, so nothing is hidden. Watch the merged array grow on the right.
| Step | i (nums1) | j (nums2) | compare | smaller → write | merged after |
|---|---|---|---|---|---|
| 1 | 0 (1) | 0 (2) | 1 ≤ 2 | take nums1 → 1 | [1] |
| 2 | 1 (3) | 0 (2) | 3 > 2 | take nums2 → 2 | [1, 2] |
| 3 | 1 (3) | 1 (4) | 3 ≤ 4 | take nums1 → 3 | [1, 2, 3] |
| 4 | 2 (5) | 1 (4) | 5 > 4 | take nums2 → 4 | [1, 2, 3, 4] |
| 5 | 2 (5) | 2 (6) | 5 ≤ 6 | take nums1 → 5 | [1, 2, 3, 4, 5] |
| 6 | 3 (7) | 2 (6) | 7 > 6 | take nums2 → 6 | [1, 2, 3, 4, 5, 6] |
| 7 | 3 (7) | j = 3 done | nums2 empty | drain nums1 → 7 | [1, 2, 3, 4, 5, 6, 7] |
Let us walk the whole trace step by step and see how the smaller value keeps winning at every turn.
Steps 1 to 6: both inputs still have numbers.
This is the main loop, where the real comparing happens. Each step lines up the current nums1 value against the current nums2 value, copies the smaller one, and slides only that finger forward. The other finger stays put, ready to be compared again next time.
See how the two fingers never rush ahead of each other. Each step advances exactly one of them, so no value is ever skipped or copied twice.
Step 7: nums2 is empty, so we drain nums1.
With j now past the end of nums2, the compare in the while loop can no longer run, so the main loop stops. One number is still unused, the 7 sitting at index 3 of nums1.
Now merged holds the full sorted result, but it is the wrong array. The final line copies merged back into nums1, value by value. After that, nums1 reads [1, 2, 3, 4, 5, 6, 7], exactly the answer we wanted. We did pay for a whole extra array to get there, though.
The logic is easy to trust, but that extra array is the weak spot. The problem practically dares you to remove it, which we do soon.
Here is the lazy shortcut. Copy nums2 into the empty tail of nums1, then sort the whole thing. It is short to write, but it throws away the fact that both inputs were already sorted.
for k from 0 to n-1:
nums1[m + k] = nums2[k] // fill the empty tail
sort nums1 // sort the full arrayThis approach trades cleverness for plain simplicity, and it works in two clear stages.
The first loop fills the empty tail. Each value of nums2 drops into the zero slots of nums1, one after another, starting at index m. The index m + k is what lines them up, since the real values already occupy slots 0 to m minus 1. After this loop, nums1 holds all m plus n real values, but the ones from nums2 sit in a jumbled block near the end.
The sort then cleans up the mess. One call reorders the whole array so every value lands in its correct place. It does not care that parts of the array were already sorted, and that is the catch.
public static void merge(int[] nums1, int m, int[] nums2, int n) {
for (int k = 0; k < n; k++) {
nums1[m + k] = nums2[k];
}
Arrays.sort(nums1);
}Inputs: nums1 = [1, 3, 5, 7, 0, 0, 0], m = 4, and nums2 = [2, 4, 6], n = 3. First we trace the copy loop, then the sort. Nothing is skipped.
Copy loop: filling the empty tail.
| Step | k | value copied | target slot | nums1 after |
|---|---|---|---|---|
| 1 | 0 | nums2[0] = 2 | index 4 | [1, 3, 5, 7, 2, 0, 0] |
| 2 | 1 | nums2[1] = 4 | index 5 | [1, 3, 5, 7, 2, 4, 0] |
| 3 | 2 | nums2[2] = 6 | index 6 | [1, 3, 5, 7, 2, 4, 6] |
Sort step: one call orders everything.
| Stage | nums1 |
|---|---|
| before sort | [1, 3, 5, 7, 2, 4, 6] |
| after sort | [1, 2, 3, 4, 5, 6, 7] |
Let us go through both stages and see exactly what each one changes in nums1.
Copy loop, steps 1 to 3.
This loop only touches the three zero slots at the end of nums1. The real values 1, 3, 5, 7 in the front are never read or moved. The index m + k, with m being 4, is what steers each value into the right blank slot.
At this point all seven real values live in nums1, but the block 2, 4, 6 sits out of order behind the 7. The array is complete but not sorted.
Sort step.
Now one sort call reorders the whole array. It shuffles values with no memory that 1, 3, 5, 7 were already in order, and that 2, 4, 6 were too. The result is [1, 2, 3, 4, 5, 6, 7], which is correct. Still, the sort re-examined values that never needed moving. So this method does strictly more work than a plain merge would.
Short code, slower class. Sorting from scratch is heavier than a plain merge, so interviewers rarely accept this one. The next approach fixes both the speed and the memory at once.
This is the answer interviewers want. Fill nums1 from its last slot backward, always placing the larger of the two current values. Because the free room sits at the end, we never overwrite a value we still need. No extra array, no full sort.
p1 = m - 1 // last real value in nums1
p2 = n - 1 // last value in nums2
p = m + n - 1 // last slot in nums1 (write position)
while p2 >= 0:
if p1 >= 0 and nums1[p1] > nums2[p2]:
nums1[p] = nums1[p1]; p1 = p1 - 1
else:
nums1[p] = nums2[p2]; p2 = p2 - 1
p = p - 1Three pointers again, but this time they all start at the back. Picture each one as a finger on the last item it cares about. Here p1 sits on the last real value of nums1, and p2 sits on the last value of nums2. The third finger, p, marks the slot we write into, starting at the very end of nums1.
The loop fills nums1 from right to left. At each turn we compare the two current values under p1 and p2, then write the larger one into slot p. This is the reverse of the front merge: instead of picking the smallest and filling from the front, we pick the largest and fill from the back. Whichever value we used, its own finger steps back by one, and the write finger p steps back too.
The safety of this whole scheme comes from where the empty room lives. The blank slots sit at the tail of nums1, so the first few writes land on those zeros. By the time p reaches the real values, those values have already been read and copied further right, so overwriting them is harmless.
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1;
while (p2 >= 0) {
if (p1 >= 0 && nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--];
} else {
nums1[p--] = nums2[p2--];
}
}
}Inputs: nums1 = [1, 3, 5, 7, 0, 0, 0], m = 4, and nums2 = [2, 4, 6], n = 3. So p1 starts at 3, p2 at 2, and p at 6. We trace every write, showing the full nums1 array after each one.
| Step | p1 (nums1) | p2 (nums2) | p (write) | compare | write → slot | nums1 after |
|---|---|---|---|---|---|---|
| 1 | 3 (7) | 2 (6) | 6 | 7 > 6 | 7 → idx 6 | [1, 3, 5, 7, 0, 0, 7] |
| 2 | 2 (5) | 2 (6) | 5 | 5 ≤ 6 | 6 → idx 5 | [1, 3, 5, 7, 0, 6, 7] |
| 3 | 2 (5) | 1 (4) | 4 | 5 > 4 | 5 → idx 4 | [1, 3, 5, 7, 5, 6, 7] |
| 4 | 1 (3) | 1 (4) | 3 | 3 ≤ 4 | 4 → idx 3 | [1, 3, 5, 4, 5, 6, 7] |
| 5 | 1 (3) | 0 (2) | 2 | 3 > 2 | 3 → idx 2 | [1, 3, 3, 4, 5, 6, 7] |
| 6 | 0 (1) | 0 (2) | 1 | 1 ≤ 2 | 2 → idx 1 | [1, 2, 3, 4, 5, 6, 7] |
| 7 | 0 (1) | p2 = -1 | — | nums2 empty | loop ends | [1, 2, 3, 4, 5, 6, 7] |
Legend: p1 is the read finger on nums1, p2 is the read finger on nums2, and p is the write finger moving backward through nums1.
This is the trace that matters most, so let us go slowly. At every step we compare the two biggest leftovers, drop the winner into the far right open slot, and step that finger back. Keep one eye on which slot gets overwritten, because that is where the in-place magic hides.
Step 1: 7 beats 6.
The two biggest leftovers are nums1[3] = 7 and nums2[2] = 6. Since 7 > 6, the 7 is the largest value in the entire merge, so its home is the very last slot, index 6.
Step 2: 6 beats 5.
Now p1 is on 5 and p2 is still on 6. Since 5 ≤ 6, the else branch runs and the 6 from nums2 takes the next slot. The 6 is the largest of the values still waiting.
Step 3: 5 beats 4.
Here p1 is on 5 and p2 is on 4. Since 5 > 4, the 5 from nums1 wins this round.
Step 4: 4 beats 3.
Now p1 is on 3 and p2 is on 4. Since 3 ≤ 4, the 4 from nums2 is larger and gets placed.
Step 5: 3 beats 2.
Here p1 is on 3 and p2 is on 2. Since 3 > 2, the 3 from nums1 wins.
Step 6: 2 beats 1.
Now p1 is on 1 and p2 is on 2. Since 1 ≤ 2, the 2 from nums2 is larger and drops in.
Step 7: nums2 is empty, loop ends.
The while check p2 >= 0 is now false, so the loop stops here. Only one value was never touched, the 1 at index 0.
Step back and notice the pattern across all seven steps. Every single write either filled a blank zero or overwrote a value we had already copied to safety. That is why one backward pass can finish the whole merge in place, with nothing but three little index variables.
| 💡 Interview Insight If asked “why compare with > and not >=?”, say it barely matters for correctness here, since equal values can go in either order. Both still produce a sorted array. |
| Approach | How it merges | Extra memory | Speed feel |
|---|---|---|---|
| New array | Front pointers into a fresh array | A whole extra array | Fast, but wasteful |
| Dump and sort | Copy tail, then full sort | In place, minus sort stack | Slower, re-sorts sorted data |
| Backward two-pointer | Back pointers, larger value wins | Just three indices | Fast and lean |
Tables are exact, but a sketch often lands faster. Here is the same backward merge drawn by hand.

All three give the same sorted nums1. They just pay different prices.
| Approach | Time | Space | Note |
|---|---|---|---|
| New array | O(m + n) | O(m + n) | Clear logic, but wastes an array |
| Dump and sort | O((m+n) log(m+n)) | O(1) or O(log n) | Short, but re-sorts sorted data |
| Backward two-pointer | O(m + n) | O(1) extra | Fast, lean, the expected answer |
The first and third share the same time class. What splits them is memory. The backward sweep carries only three little indices, while the new-array version drags a full copy along.
In an interview, start with the new-array merge, point out the wasted space, then flip to the backward sweep and explain why filling from the end is safe. That climb is the story interviewers want to hear.
| 💡 Interview Insight If pushed on the sort approach, admit it is the easiest to write but throw in the catch: it ignores the sorted inputs, so it runs in O((m+n) log(m+n)) instead of the clean O(m+n) a real merge gives. |
A few small traps catch beginners on Merge Sorted Array. Keep them in mind.
Run the m = 0 and n = 0 cases through your code before you call it done. They catch more bugs than any ordinary input will.
A: The empty space in nums1 sits at its end. Filling forward would overwrite nums1 values you have not read yet, but writing from the back always lands on a slot you no longer need, so the merge stays safe.
A: The backward two-pointer merge runs in O(m + n) time and uses only O(1) extra space, since it works inside nums1 with just three index variables.
A: Yes, and it works, but it ignores that both inputs are already sorted. That makes it O((m+n) log(m+n)) instead of the clean O(m+n) a real merge gives, so interviewers rarely accept it.
A: If n is 0, nums2 is empty and nums1 is already the answer. If m is 0, every value lives in nums2, so the loop simply copies all of nums2 across. Both are worth testing before you finish.
A: Once every nums1 value is placed, p1 drops below zero. The guard stops the code from reading past the front of nums1, which would otherwise crash while nums2 still has values left to place.
Merge Sorted Array in Java looks harmless, yet the in-place rule quietly raises the bar. The trick is to stop fighting the empty space and start using it.
Our seven-number trace made the payoff clear. The new-array merge copied everything twice. The backward sweep filled nums1 from the end and finished in one clean pass, with no extra array at all.
So take the pattern, not just the answer. When free room sits at one end, fill from that end. Compare the biggest leftovers, drop the winner in, and let the pointers walk back. That habit will serve you on many array problems waiting further down the list.
javahandson.com | DSA Series | Arrays & Strings