Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
The biggest squares live at the two ends (largest negative or largest positive), not the middle. Put a pointer at each end, compare absolute values, square the larger, and write it into the result from the back forward. O(n) time, O(1) extra space — beats the obvious square-then-sort O(n log n).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you exploit the sorted-but-signed structure to merge in O(n) instead of defaulting to square-then-sort O(n log n). It's an 'easy' that rewards seeing the U-shape and the two-monotonic-runs insight.
Where candidates slip
Squaring and sorting (correct but suboptimal), or trying to fill the result front-to-back and getting tangled — the largest values appear at the ends, so back-to-front is the natural fill order.
How to stand out
Say 'after squaring, magnitudes form two sorted runs from the center outward; I'll merge them from the ends into the result back-to-front, O(n) time, O(1) extra space.' Then mention this is exactly mergesort's merge.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewSorted array with negativesabs(nums[lo]) vs abs(nums[hi])?left biggerright biggerExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Square Then Sort (Brute Force)
1Hint 1Explore hintHide hint
Ignore the sorted property for a second — what definitely produces the right answer regardless of input order?
2Hint 2Explore hintHide hint
Square every element, then sort the resulting array.
3Hint 3Explore hintHide hint
Correct and short, but the sort costs O(n log n) and ignores that the input was already sorted. The follow-up explicitly asks for better.
A · Core idea
The bulletproof baseline: map each value to its square, then sort. It works on any integer array, sorted or not.
Why it's worth stating. It's correct in three lines and demonstrates you can ship something immediately. It also sets up the contrast the interviewer wants: 'this ignores that the input is already sorted.'
Why it leaves performance on the table. The input arrives sorted, and squaring corrupts that order only in a structured way (the U-shape). Re-sorting from scratch pretends we know nothing about the data — paying log n for information we already have. The follow-up line in the prompt is a direct invitation to do better.
Why we present it anyway. Interviewers grade the progression. Stating the O(n log n) baseline and then replacing it with the O(n) merge shows you can both produce a working solution and refine it — exactly the signal an 'easy with a follow-up' is designed to elicit.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [-4, -1, 0, 3, 10]square
[-4,-1,0,3,10] → [16,1,0,9,100]
unsorted squares[0, 1, 9, 16, 100]C · Code with teaching comments
Reference implementation previewpublic int[] sortedSquares(int[] nums) { int n = nums.length; int[] res = new int[n]; for (int i = 0; i < n; i++) { res[i] = nums[i] * nums[i];Explore the full codeHide the full code
D · Why it works and complexityO(n log n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
When a problem hands you a sorted input and then asks a follow-up for sub-n log n, that's a near-certain hint that a linear merge or two-pointer pass exists — the sort is the thing to eliminate.
ComplexityO(n log n) time, O(n) space — Squaring is O(n); sorting the squares dominates at O(n log n). Output (and the sort) use O(n) space.
E · Edge cases and pitfalls3Stopping here when the follow-up explicitly asks for O(n).Explore edge casesHide edge cases
Pitfalls
- Stopping here when the follow-up explicitly asks for O(n).
- Sorting the original values instead of the squares.
- Forgetting that negatives can outrank positives after squaring.
Approach 2 of 2 · Optimal
Two Pointers Filling Back-to-Front (Optimal)
1Hint 1Explore hintHide hint
After squaring, where does the single largest value live — the middle, or an end? Try it on [-4, -1, 0, 3, 10].
2Hint 2Explore hintHide hint
It's always at one of the two ends (largest negative or largest positive). Compare absolute values at lo and hi to find it.
3Hint 3Explore hintHide hint
Allocate the result and fill it from the LAST index backward: write the bigger square, move that pointer inward, decrement the write position.
A · Core idea
Set lo = 0, hi = n − 1, and a write position pos = n − 1. At each step compare |nums[lo]| with |nums[hi]|; the larger magnitude squares to the larger value, so write that square at res[pos], move the chosen pointer inward, and decrement pos. Continue until every slot is filled.
Why the largest square is always at an end. The array is sorted, so the most negative value sits at the far left and the most positive at the far right. Squaring maps both to large positives. Any element between them has a magnitude no larger than at least one boundary value (magnitudes decrease as you move toward zero and increase away from it). So the maximum square is guaranteed to be at lo or hi — never strictly inside. That's the structural fact that makes a two-pointer merge correct.
Why we fill back-to-front. Because we can cheaply identify the current largest remaining square (it's at one end), it's natural to place values from largest to smallest — i.e., from the last index of the result toward index 0. Trying to fill front-to-front would require us to find the current smallest square, which can be at either end too, but back-to-front pairs perfectly with the 'max is at an end' fact and keeps the logic clean.
Why compare absolute values, not raw values. A raw comparison would say -4 < 3, but (-4)² = 16 > 9 = 3². The square depends only on magnitude, so the decision 'which end holds the bigger square' is exactly 'which end has the bigger absolute value'. Comparing raw signed values here is the classic bug.
Why this is just mergesort's merge. Read the array from the center outward in both directions and you get two already-sorted sequences of magnitudes (the left run descending toward zero, the right run ascending). Squaring preserves the order within each run. Merging two sorted runs is linear — and that's precisely what these two pointers do, except we emit the largest element each step and write it at the back.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [-4, -1, 0, 3, 10]Array state
lo=0, hi=4 → pos=4
|−4|=4 < |10|=10 → write 100 at index 4
[0, 1, 9, 16, 100]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Reframe the input as two monotonic runs of magnitudes meeting at the value closest to zero. Squaring keeps each run sorted, so the problem is a single merge of two sorted lists — O(n). The 'compare absolute values, write the bigger one at the back' move is the merge step; recognising sorted-runs-after-a-transform is the transferable skill.
ComplexityO(n) time, O(1) space — Each element is consumed exactly once as lo rises or hi falls, and each produces one write → O(n) time. Apart from the required output array, only two indices → O(1) extra space.
E · Edge cases and pitfalls8nums = [5] — Single element: one iteration writes 25 → [25].Explore edge casesHide edge cases
Edge cases handled
nums = [5]Single element: one iteration writes 25 → [25].nums = [-5, -3, -1]All negative: left magnitudes are largest, so lo keeps winning → [1, 9, 25].nums = [1, 2, 3]All non-negative: hi keeps winning → [1, 4, 9]; order already preserved.nums = [-3, 3]Equal magnitudes: tie goes to the else (right) branch first → both 9s placed → [9, 9].
Pitfalls
- Comparing raw values (nums[lo] vs nums[hi]) instead of absolute values/squares.
- Filling the result front-to-back — the largest values belong at the back.
- Off-by-one on pos or the lo/hi bounds, leaving a slot unwritten.
- Appending to a list instead of indexing res[pos], producing reversed output.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[] sortedSquares(int[] nums) { int n = nums.length; int[] res = new int[n]; int lo = 0, hi = n - 1; for (int pos = n - 1; pos >= 0; pos--) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Comparing signed values instead of magnitudes
−4 < 3 by sign, but (−4)² = 16 > 9 = 3². The next-largest square depends on absolute value, not sign.
Explore the code comparisonHide the code comparison
Comparing signed values instead of magnitudes
−4 < 3 by sign, but (−4)² = 16 > 9 = 3². The next-largest square depends on absolute value, not sign.
Don't write this
if (nums[lo] > nums[hi]) {
res[pos] = nums[lo] * nums[lo];
lo++;
}Write this instead
int left = nums[lo] * nums[lo], right = nums[hi] * nums[hi];
if (left > right) {
res[pos] = left;
lo++;
}2Filling front-to-back
The two-pointer comparison cheaply identifies the current LARGEST remaining square, which must go in the last open slot — filling forward inverts the order.
Explore the code comparisonHide the code comparison
Filling front-to-back
The two-pointer comparison cheaply identifies the current LARGEST remaining square, which must go in the last open slot — filling forward inverts the order.
Don't write this
pos = 0
for ... :
res[pos] = larger_square
pos += 1Write this instead
for pos in range(n - 1, -1, -1):
res[pos] = larger_square3Defaulting to sort and ignoring the follow-up
Square-then-sort is correct but O(n log n); the prompt's follow-up explicitly asks for the O(n) two-pointer merge.
Explore the code comparisonHide the code comparison
Defaulting to sort and ignoring the follow-up
Square-then-sort is correct but O(n log n); the prompt's follow-up explicitly asks for the O(n) two-pointer merge.
Don't write this
return sorted(x * x for x in nums) # ignores the O(n) follow-upWrite this instead
# two-pointer merge filling res back-to-front → O(n)Revise
Carry the pattern into the interview
Pattern to remember
Opposite-end two pointers filling the result back-to-front
Code pattern to memorize
lo, hi = 0, n - 1
for pos in range(n - 1, -1, -1): # fill from the END
if abs(nums[lo]) > abs(nums[hi]):
res[pos] = nums[lo] * nums[lo]; lo += 1
else:
res[pos] = nums[hi] * nums[hi]; hi -= 1Rules to remember
- 1The largest square is at one of the two ends, so fill the result back-to-front (largest first).
- 2Compare absolute values (
abs(nums[lo])vsabs(nums[hi])) to decide which end to consume — squaring a negative can dominate. - 3Use
>=/elseconsistently so ties are handled deterministically; either end is fine on a tie. - 4Allocate the result array up front and write by index, never append in the wrong order.
- 5Don't square-then-sort — it works but throws away the sorted input and costs an extra log n factor.
Use this pattern when
- Input is **sorted**, but a transform (squaring) breaks that order in a predictable, U-shaped way
- Extremes of the array hold the extremes of the transformed values
- You want O(n) by merging two monotonic runs instead of re-sorting
- Need a sorted output and can write it positionally
Choose another pattern when
- Input is **unsorted** → just square and sort, O(n log n) is unavoidable
- Transform preserves order (e.g. all non-negative) → trivial map, no two pointers needed
- You need the squares in original order, not sorted → no merge trick applies
- Values can overflow when squared and language has no big ints → mind the type
Interview takeaway
A sorted array of mixed signs squares into a U-shape: descending magnitudes on the left, ascending on the right. That's two sorted runs meeting in the middle — merge them from the outside in, filling the answer back-to-front, for O(n)/O(1). It's the merge step of mergesort in disguise.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternTwo-pointer merge of two monotonic runs, filling the result back-to-front. Recognise it when a sorted input is transformed (squaring) into a predictable U-shape that's really two sorted sequences. Same engine as the merge step of mergesort and Merge Sorted Array.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Merge Sorted ArraySame back-to-front fill trick — merge from the ends to avoid overwriting unprocessed values.
- Two Sum II — Input Array Is SortedOpposite-end pointers on a sorted array, moving based on a comparison.
- Sort ColorsComing soonMulti-pointer partitioning of a constrained array in O(n)/O(1).
- Merge Two Sorted ListsComing soonThe merge step itself — two sorted sequences combined in linear time.