Merge Sorted Array in Java DSA: Brute Force to the Merge-From-Back Trick

  • Last Updated: July 25, 2026
  • By: javahandson
  • Series
img

Merge Sorted Array in Java DSA: Brute Force to the Merge-From-Back Trick

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.

1. Introduction

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.

2. Understanding the Problem

Let us pin down the rules before any code. A clear problem statement now saves you from silent bugs later.

  • You get two integer arrays, nums1 and nums2, and both are already sorted.
  • nums1 has length m + n. The first m slots hold real values, and the last n slots are zeros.
  • nums2 has n real values that must be folded into nums1.
  • You must merge them so nums1 ends up sorted, using the space nums1 already has.
  • You do not return anything. The caller reads nums1 afterwards.

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.

2.1 Why The Empty Slots Matter

The zeros at the end of nums1 are the whole point. They tell you exactly how much room you have to work with.

  • The count of zeros equals n, the length of nums2.
  • So nums1 always has just enough space for every value in nums2.
  • That means you never need to grow nums1 or build a fresh array.

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.

2.2 Why This Example

Our two arrays are picked to be a little tricky. That keeps the traces honest.

  • Both arrays share the value 2, so we can see how ties are handled.
  • nums2 ends with 6, the largest value, so it must travel to the very last slot.
  • nums1 starts with 1, the smallest value, so its front should stay untouched.

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.

3. Concepts You Need Here

Two small ideas carry this whole problem. Both return often in later array questions, so learn them well.

3.1 Merging Two Sorted Lists

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.

  • You keep one finger on each list, both at the front.
  • Then you compare the two values your fingers point at.
  • Next you take the smaller one, place it, and move that finger forward.

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.

3.2 Filling From The Back

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.

  • We start three fingers at the last used position of each side.
  • Then we compare the two largest remaining values.
  • Finally we drop the bigger one into the last empty slot, then step that finger back.

Think of stacking plates from the top of a cupboard down. The tallest plates go highest, and you never disturb the ones already placed.

4. Approach 1: Brute Force

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.

4.1 Pseudocode

for j from 0 to n-1:        // copy nums2 into the tail
    nums1[m + j] = nums2[j]
 
sort nums1 in ascending order

4.2 Reading the Pseudocode

Let 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.

  • The target slot is nums1[m + j], not nums1[j]. That offset is the important part.
  • Why add m? Because the first m slots of nums1 already hold real values. We must not stomp on them.
  • So the first value of nums2 lands at slot m, the next at slot m + 1, and so on into the zeros.

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.

4.3 Java Code

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]
    }
}

4.4 Reading the Java Code

Each part here has one clear job. Let us walk through them slowly.

  • Line 6 starts a loop over every index of nums2, using j.
  • Inside it, line 7 copies nums2[j] into nums1[m + j], filling the empty tail.
  • After the loop, line 9 sorts the whole of nums1 into ascending order.
  • The main method sets up our example and prints the merged result.

One detail deserves a second look. The offset m + j is what keeps the two halves from colliding.

  • Without the m, we would overwrite the real values at the front of nums1.
  • With the m, every copy lands safely inside the zero region.
  • The sort then blends the two halves together.

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.

4.5 Dry Run of the Brute Force

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]

4.6 Reading the Dry Run

Three copies filled the tail, then one sort finished it. Let us break that down.

  • Steps 1 to 3 copied 2, 5 and 6 into the zero slots in order.
  • After the copies, nums1 held every value but was not sorted.
  • The sort then swept through and lifted the middle 2 into place.

Now look at the hidden cost. The sort touches the whole array again, even the parts that were already sorted.

  • Both inputs arrived sorted, so most of that work was wasted.
  • A sort of six values is cheap, but a sort of a million is not.
  • That wasted effort is exactly what the next approaches remove.

4.7 Time and Space Cost

Brute force copies in linear time, but the sort dominates. So the time is driven by the sort.

  • Time is O((m + n) log(m + n)), because of the sort at the end.
  • Space is O(1) extra if the sort is in place, since we reuse nums1.

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.

5. Approach 2: Merge With a Helper Array

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.

5.1 Pseudocode

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 nums1

5.2 Reading the Pseudocode

This 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.

  • i is the bookmark for nums1. It starts at 0, the first real value.
  • j is the bookmark for nums2. It also starts at 0.
  • k is the bookmark for temp. It marks the next empty slot to fill.

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.

  • If nums1[i] is smaller or equal, we copy it into temp[k] and push i forward.
  • Otherwise nums2[j] is smaller, so we copy that into temp[k] and push j forward.
  • Either way, we then push k forward, because one slot of temp is now filled.

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.

  • The second loop copies any leftover nums1 values straight into temp.
  • The third loop copies any leftover nums2 values straight into temp.
  • Only one of these two loops ever does real work, because only one side can have leftovers.

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.

5.3 Java Code

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]
    }
}

5.4 Reading the Java Code

The shape follows the pseudocode closely, so let us focus on the Java details.

  • Line 4 makes the helper array temp with room for all m + n values.
  • Line 5 sets the three fingers i, j and k to 0.
  • Lines 7 to 13 merge the two fronts, always taking the smaller value.
  • The k++ and i++ notation copies first, then moves the finger one step.
  • Lines 14 to 19 drain whichever side still has leftovers.
  • Lines 21 to 23 copy the finished temp back into nums1.

Compare this with the brute force. There is no sort here at all.

  • Brute force asked, how do I reorder this whole jumble?
  • This version asks, which front value is smaller right now?
  • That smaller question is why we skip the sort and stay linear.

5.5 Dry Run of the Helper Approach

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.

5.6 Reading the Dry Run

Four compares merged the fronts, then two drains finished it. Let us look closely.

  • Steps 1 and 2 took 1 and 2 from nums1, since they were smaller or equal.
  • Step 3 hit a tie between 3 and 2, so the smaller 2 from nums2 went in.
  • Step 4 took 3 from nums1, which emptied the nums1 side.

The last two steps are the drain. Only nums2 had leftovers.

  • nums1 ran out after step 4, so its drain loop did nothing.
  • The nums2 drain then copied 5 and 6 straight into temp.
  • Finally temp was copied back into nums1 as [1, 2, 2, 3, 5, 6].

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.

6. Approach 3: Merge From the Back In Place

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.

6.1 Pseudocode

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 - 1

6.2 Reading the Pseudocode

This 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.

  • i starts at m – 1, the last real value inside nums1.
  • j starts at n – 1, the last value inside nums2.
  • k starts at m + n – 1, the very last slot of nums1.

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.

  • The 2nd argument is m = 3, the number of real values in nums1. Even though nums1 has 6 slots, only 1, 2 and 3 are real; the three zeros are empty placeholders.
  • The 4th argument is n = 3, the number of values in nums2, which are 2, 5 and 6.

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.

  • Here m sets i to 2, the index of the last real value, 3.
  • And n sets j to 2, the index of the last nums2 value, 6.
  • Together they set k to m + n – 1, which is 5, the final slot to fill.

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.

  • If nums1[i] is bigger than nums2[j], we place nums1[i] at k and step i back.
  • Otherwise nums2[j] is bigger or equal, so we place that at k and step j back.
  • Either way we step k back, because that slot is now filled.

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.

6.3 Java Code

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]
    }
}

6.4 Reading the Java Code Line by Line

This version does the most thinking in the fewest lines, so let us go gently.

  • Lines 4 to 6 set i, j and k to the last index of each region.
  • Line 8 loops while nums2 still has a value to place.
  • Line 9 checks the guard and compares the two tail values.
  • Lines 10 to 11 place the bigger nums1 value and step i back.
  • Lines 13 to 14 place the nums2 value and step j back.
  • Line 16 steps k back after every placement.

Two details separate this from the helper version.

  • There is no temp array, because we write straight into the free tail.
  • The loop ends when j hits -1, leaving the nums1 front already correct.

Because each value is placed exactly once, one backward sweep is enough. That single idea is what makes this solution shine.

6.5 Dry Run of the Merge From Back

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.

6.6 Reading the Dry Run

Four steps end the whole thing. Let us look at each one closely.

  • Step 1 compares 3 and 6, so the bigger 6 drops into slot 5.
  • Next, step 2 compares 3 and 5, so 5 drops into slot 4.
  • Then step 3 compares 3 and 2, and this time nums1 wins, so 3 drops into slot 3.
  • Finally step 4 hits a tie of 2 and 2, so nums2 gives its 2 to slot 2, and j falls to -1.

Step 4 is worth studying. The loop stops here, even though i still points at a value.

  • After placing that last 2, j becomes -1 and the loop exits.
  • The values 1 and 2 already sit correctly at the front of nums1.
  • So no extra work is needed, and the answer is [1, 2, 2, 3, 5, 6].

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.

6.7 Comparing the Three Traces

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.

7. The Dry Run on Paper

Tables are precise, but a sketch often lands faster. Here is the same merge-from-back trace drawn by hand.

Pen and paper dry run of the Merge Sorted Array merge-from-back approach in Java dsa

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.

  • A navy box marks a step where nums2 wins the compare and its value drops in.
  • A gold box marks the step where nums1 finally wins with its value 3.
  • The green box marks the last step, where the tie is settled and the array is done.

Some people read pictures faster than tables. Use whichever version clicks for you, or sketch your own on paper while you follow the code.

8. Comparing the Three Approaches

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.

9. Common Mistakes and Edge Cases

A few small traps catch beginners here. Keep them in mind.

  • Filling from the front is the most common slip. It overwrites values you still need.
  • Forgetting the “i >= 0” guard crashes when nums1 empties before nums2.
  • When n is 0, nums2 is empty, so nums1 should stay exactly as it is.
  • When m is 0, nums1 is all zeros, so nums2 simply copies straight in.
  • Equal values, like the two 2s here, must merge without dropping either one.

Run those edge cases through your code before you say you are finished. They catch more bugs than any normal input will.

10. Interview Questions

Q: Why is nums1 padded with zeros in the merge sorted array problem?

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.

Q: Why merge from the back instead of the front?

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.

Q: What is the time and space complexity of the optimal merge?

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.

Q: What happens when one array runs out during the merge?

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.

11. Conclusion

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.

12. Further Reading

Leave a Comment