Merge Sorted Array in Java DSA: Brute Force to the Merge-From-Back Trick
-
Last Updated: July 25, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Learn merge sorted array in Java DSA from brute force to the optimal in-place merge-from-back approach, with clean code, dry runs, and complexity comparisons.
Merge Sorted Array in Java is the sixth problem in this series. It comes right after Move Zeroes, and it builds on the same in-place habit. Here you learn to blend two sorted arrays into one, without a second array to lean on.
The setup is a little unusual. You get two sorted arrays. The first one already has empty slots at the end, waiting to hold the second array. Your job is to fill those slots so the whole thing stays sorted.
That extra space is a gift, and most beginners miss it. You do not need to build anything new. You just need to place numbers into slots you already own.
We build the answer in three steps. Brute force dumps everything together and sorts. A cleaner version merges with a helper array. The final trick fills from the back and needs no extra array at all.
Every approach here comes with a full dry run. We trace the same two arrays through each one, so you can watch the wasted memory disappear step by step.
Let us pin down the rules before any code. A clear problem statement now saves you from silent bugs later.
For our example, nums1 = [1, 2, 3, 0, 0, 0] with m = 3, and nums2 = [2, 5, 6] with n = 3. The three zeros are placeholders, not real data. After merging, nums1 becomes [1, 2, 2, 3, 5, 6].
Notice the trap. Those trailing zeros are not values to keep. They mark the empty room where nums2 will land.
The zeros at the end of nums1 are the whole point. They tell you exactly how much room you have to work with.
This is why the problem gives nums1 a length of m + n up front. The room is prepaid, and a good solution simply uses it.
Our two arrays are picked to be a little tricky. That keeps the traces honest.
A tidy pair like [1, 0] and [2] hides all of this. A mixed pair shows you what the pointers really do.
| 💡 Interview Insight Interviewers often ask why the array is padded with zeros instead of being exactly length m. The padding is what lets you solve it in place. Pointing that out early shows you read the constraints, not just the title. |
Two small ideas carry this whole problem. Both return often in later array questions, so learn them well.
Merging means walking two sorted lists at once and picking the smaller value each time. It is the heart of merge sort, so it is worth knowing cold.
Because both lists are sorted, the smaller of the two front values is always the smallest value left overall. That single fact makes the merge correct.
Here is the idea that makes the optimal solution click. Instead of filling nums1 from the front, we fill it from the back.
Why the back? The empty slots sit at the back. If we wrote from the front, we might overwrite a value in nums1 before we had used it. Writing from the back means we always place into empty space.
Think of stacking plates from the top of a cupboard down. The tallest plates go highest, and you never disturb the ones already placed.
The first idea most people reach for feels natural. Copy nums2 into the empty slots of nums1, then sort the whole thing. It ignores the fact that both arrays are already sorted, but it works.
It is simple, and it proves you understand the goal. The cost is a sort you did not really need, which we will remove later.
for j from 0 to n-1: // copy nums2 into the tail
nums1[m + j] = nums2[j]
sort nums1 in ascending orderLet us read this slowly, one block at a time. The plan is almost lazy: pour everything into one array, then let a sort clean up the order. Think of tipping two sorted decks of cards into one pile and reshuffling into order.
Look at the first loop. It walks through nums2 with an index called j, from the first value to the last. For each value in nums2, it copies that value into nums1.
Let us picture it on our arrays. Here m is 3, so nums2[0] goes to nums1[3], nums2[1] goes to nums1[4], and nums2[2] goes to nums1[5]. After this loop, nums1 holds all six real values, but they sit in a jumbled order like [1, 2, 3, 2, 5, 6].
Now the second line. We sort nums1 from smallest to largest. The sort does not care that parts of the array were already sorted. It simply reorders every value until the whole array climbs upward.
After the sort, nums1 reads [1, 2, 2, 3, 5, 6], which is our answer. So the whole plan is two moves: copy nums2 into the tail, then sort everything into place.
import java.util.Arrays;
public class MergeSortedBrute {
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)); // [1, 2, 2, 3, 5, 6]
}
}Each part here has one clear job. Let us walk through them slowly.
One detail deserves a second look. The offset m + j is what keeps the two halves from colliding.
This code is short and hard to get wrong. Its weakness is the sort, which throws away the fact that both inputs were already sorted. We fix that next.
Let us trace nums1 = [1, 2, 3, 0, 0, 0] and nums2 = [2, 5, 6]. Watch the tail fill during the copy loop.
| Step | j | nums2[j] | Target slot | nums1 after this step |
|---|---|---|---|---|
| 1 | 0 | 2 | nums1[3] | [1, 2, 3, 2, 0, 0] |
| 2 | 1 | 5 | nums1[4] | [1, 2, 3, 2, 5, 0] |
| 3 | 2 | 6 | nums1[5] | [1, 2, 3, 2, 5, 6] |
The copy loop ends with nums1 holding [1, 2, 3, 2, 5, 6]. The values are all present, but the 2 in the middle is out of order.
| Stage | What happens | nums1 now |
|---|---|---|
| Sort | Reorder every value ascending | [1, 2, 2, 3, 5, 6] |
Three copies filled the tail, then one sort finished it. Let us break that down.
Now look at the hidden cost. The sort touches the whole array again, even the parts that were already sorted.
Brute force copies in linear time, but the sort dominates. So the time is driven by the sort.
Our six values sorted in a blink. Grow the arrays to a million each and the log factor starts to bite. The next version drops the sort by using the order we were handed.
The brute force sorted work it did not need to. Both arrays came sorted, so we can merge them directly. We just compare fronts and pick the smaller value each time.
The simplest way to do this uses a helper array. We merge into the helper, then copy it back into nums1. It is clear and correct, and it sets up the final trick.
temp = new array of size m + n
i = 0, j = 0, k = 0
while i < m and j < n: // both sides still have values
if nums1[i] <= nums2[j]:
temp[k] = nums1[i]; i = i + 1
else:
temp[k] = nums2[j]; j = j + 1
k = k + 1
while i < m: // drain leftover nums1
temp[k] = nums1[i]; i = i + 1; k = k + 1
while j < n: // drain leftover nums2
temp[k] = nums2[j]; j = j + 1; k = k + 1
copy temp back into nums1This one has three loops, so let us slow right down. The big idea is a helper array named temp that will hold the fully merged result. We fill temp in order, then pour it back into nums1 at the very end.
Before anything runs, we set up three fingers. Picture each as a bookmark with a clear job.
Now the first loop, which runs while both i and j still point at real values. This is the true merge. At each turn, we compare nums1[i] with nums2[j] and take the smaller one.
Why does taking the smaller front value work? Both arrays are sorted, so the smaller of the two fronts is the smallest value left anywhere. Take it, and temp stays sorted as it grows.
The loop stops the moment one side runs out. But the other side may still have values waiting. That is what the two drain loops handle.
There is one last line, and beginners forget it. We copy temp back into nums1, slot by slot. The caller only ever reads nums1, so the answer must end up there. If we leave it sitting in temp, the outside world never sees it.
So the plan reads as three simple moves: merge the two fronts into temp, drain whatever is left, then copy temp home.
public class MergeSortedHelper {
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int[] temp = new int[m + n];
int i = 0, j = 0, k = 0;
while (i < m && j < n) {
if (nums1[i] <= nums2[j]) {
temp[k++] = nums1[i++];
} else {
temp[k++] = nums2[j++];
}
}
while (i < m) {
temp[k++] = nums1[i++];
}
while (j < n) {
temp[k++] = nums2[j++];
}
for (int x = 0; x < m + n; x++) {
nums1[x] = temp[x];
}
}
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(java.util.Arrays.toString(nums1)); // [1, 2, 2, 3, 5, 6]
}
}The shape follows the pseudocode closely, so let us focus on the Java details.
Compare this with the brute force. There is no sort here at all.
Same two arrays. Watch temp fill in sorted order as the fingers advance.
| Step | i | j | Compare | Take | temp after this step |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 vs 2 | nums1 (1) | [1, _, _, _, _, _] |
| 2 | 1 | 0 | 2 vs 2 | nums1 (2) | [1, 2, _, _, _, _] |
| 3 | 2 | 0 | 3 vs 2 | nums2 (2) | [1, 2, 2, _, _, _] |
| 4 | 2 | 1 | 3 vs 5 | nums1 (3) | [1, 2, 2, 3, _, _] |
| 5 | 3 | 1 | nums1 done | drain 5 | [1, 2, 2, 3, 5, _] |
| 6 | 3 | 2 | nums1 done | drain 6 | [1, 2, 2, 3, 5, 6] |
Legend: i walks nums1, j walks nums2. “Take” shows which value moved into temp this step.
Four compares merged the fronts, then two drains finished it. Let us look closely.
The last two steps are the drain. Only nums2 had leftovers.
Notice the cost. The helper array temp is a whole second array the size of nums1. That memory is the pain point we remove next.
| 💡 Interview Insight A sharp follow-up asks what happens on a tie, like 3 versus 2 above. Using <= rather than < keeps the merge stable, meaning equal values keep their original relative order. Mentioning stability shows you think past just correctness. |
The helper version is clean, but it wastes a whole array. We already own the empty slots in nums1, so why copy anywhere else? The catch is that filling from the front could overwrite values we still need.
The fix is to fill from the back. Largest values go into the last empty slots first. Because we only ever write into space that is already free, nothing important gets overwritten.
i = m - 1 // last real value in nums1
j = n - 1 // last value in nums2
k = m + n - 1 // last slot overall
while j >= 0:
if i >= 0 and nums1[i] > nums2[j]:
nums1[k] = nums1[i]; i = i - 1
else:
nums1[k] = nums2[j]; j = j - 1
k = k - 1This is the tightest version, so let us take it gently. There is one loop and no helper array. The magic is direction: we walk backwards, placing the biggest values first, right where the empty room is.
We set up three fingers, and each starts at the end of its own region.
Notice that all three fingers depend on m and n. So it is worth pausing on the call itself, merge(nums1, 3, nums2, 3). Those two 3s are m and n, the counts of real values in each array.
So why not just use nums1.length instead of m? Because nums1.length is 6, but only 3 of those slots hold real data. If the code trusted the length, it would treat the placeholder zeros as real numbers and merge them too, giving a wrong answer.
It is only a coincidence that both are 3 here. If nums1 were [1, 2, 3, 4, 0, 0] and nums2 were [5, 6], the call would be merge(nums1, 4, nums2, 2). The length of nums1 stays 6, but now m is 4 and n is 2.
The loop runs while j is still valid, that is, while nums2 has values left to place. At each turn we compare the two largest remaining values and drop the bigger one into slot k.
Watch that guard, “i >= 0”. It protects us when nums1 runs out of real values but nums2 still has some. In that case we simply keep taking from nums2. We never need the reverse guard on j, because the loop already stops the moment j drops below 0.
Here is the key insight that makes this safe. The slot k we write into is always at or ahead of the value at i we might read. So we never overwrite a nums1 value before using it. The empty room always stays one step ahead of us.
And why does the loop stop when j hits -1 rather than when both fingers finish? Because any nums1 values still sitting at the front are already in their correct sorted place. Once nums2 is fully placed, there is simply nothing left to do.
So the plan is one backward sweep: compare the two tails, drop the bigger into the last empty slot, and stop when nums2 is used up.
public class MergeSortedBack {
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];
i--;
} else {
nums1[k] = nums2[j];
j--;
}
k--;
}
}
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(java.util.Arrays.toString(nums1)); // [1, 2, 2, 3, 5, 6]
}
}This version does the most thinking in the fewest lines, so let us go gently.
Two details separate this from the helper version.
Because each value is placed exactly once, one backward sweep is enough. That single idea is what makes this solution shine.
Now we trace the same arrays one final time. Watch k fill the tail from right to left.
| Step | i | j | k | Compare | Action | nums1 after this step |
|---|---|---|---|---|---|---|
| 1 | 2 | 2 | 5 | 3 vs 6 | place 6 at k=5 | [1, 2, 3, 0, 0, 6] |
| 2 | 2 | 1 | 4 | 3 vs 5 | place 5 at k=4 | [1, 2, 3, 0, 5, 6] |
| 3 | 2 | 0 | 3 | 3 vs 2 | place 3 at k=3 | [1, 2, 3, 3, 5, 6] |
| 4 | 1 | 0 | 2 | 2 vs 2 | place 2 at k=2 | [1, 2, 2, 3, 5, 6] |
Legend: i is the nums1 tail, j is the nums2 tail, k is the next empty slot from the right.
Four steps end the whole thing. Let us look at each one closely.
Step 4 is worth studying. The loop stops here, even though i still points at a value.
There is a quiet promise hidden in this loop. Every value is placed exactly once, and the front of nums1 is never disturbed. You never need a second pass to tidy up.
Same arrays, same answer, three very different amounts of work and memory.
| Approach | Main work | Extra memory used |
|---|---|---|
| Brute force | Copy, then full sort | None (in-place sort) |
| Helper array | One forward merge | A second array of 6 |
| Merge from back | One backward merge | Three int pointers |
The gap grows with size. At six values the difference looks small. At six million the helper array doubles your memory, while the back merge barely uses any.
| 💡 Interview Insight Expect this question: why not merge from the front in place? Writing from the front could overwrite a nums1 value before you read it, forcing shifts that cost O(m*n). The back merge dodges that by writing only into free space. |
Tables are precise, but a sketch often lands faster. Here is the same merge-from-back trace drawn by hand.

The array sits along the top with its indices. Each step block on the left shows the two values we compare and the choice we make. The panel on the right shows nums1 after that step.
Follow the colour cues as you read it.
Some people read pictures faster than tables. Use whichever version clicks for you, or sketch your own on paper while you follow the code.
All three give a correct answer. They just pay different prices for it.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O((m+n) log(m+n)) | O(1) | Simple, but wastes a sort |
| Helper array | O(m+n) | O(m+n) | Linear, but uses a second array |
| Merge from back | O(m+n) | O(1) | In place, single pass, expected answer |
Notice the story here. The helper array already runs in linear time, so speed is not the final win. The real prize is space, and the back merge gets linear time with no extra array at all.
In an interview, start with brute force to show you understand the goal. Then explain the wasted sort you spotted. Finally tighten it into the merge from back.
That climb from wasteful to lean is exactly the story interviewers want to hear. Jumping straight to the optimal answer skips the reasoning they came to observe.
| 💡 Interview Insight If asked to justify the in-place claim, point out that the back merge adds only three integer variables. No array grows, and no second buffer appears, so the extra space stays constant no matter how large the inputs get. |
A few small traps catch beginners here. Keep them in mind.
Run those edge cases through your code before you say you are finished. They catch more bugs than any normal input will.
A: The trailing zeros mark empty room equal to the length of nums2. That prepaid space is what lets you merge fully in place, without building a second array.
A: The empty slots sit at the back. Writing from the front could overwrite a nums1 value before you use it, forcing costly shifts. Filling from the back always writes into free space, so nothing is lost.
A: The merge-from-back approach runs in O(m + n) time and O(1) extra space. It places each value exactly once and uses only three integer pointers.
A: The loop stops once nums2 is fully placed. Any remaining nums1 values already sit correctly at the front, so no extra work is needed. The i >= 0 guard handles the reverse case, where nums1 empties first.
Merge Sorted Array in Java looks like a warm-up, and in a way it is. But the merge-from-back pattern inside it is a real skill.
Our small trace showed the payoff clearly. The helper version needed a second array of the same size. The back merge needed just three variables and a single sweep.
So take the pattern, not just the answer. When an array already has free space at the end, think about filling it from the back. That one habit turns a wasteful copy into a clean in-place merge.
That trick will return in harder problems further down the list. The two-pointer, fill-from-the-end idea is one of the most reused tools in array work.