Given an integer array, sort the array in ascending order.

Example:

Input: nums = [6, 8, 4, 2, 7, 3, 1, 5]
Output: [1, 2, 3, 4, 5, 6, 7, 8]

In Plain English

New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.

📚 Real-world analogy: tidying a shelf of books

Think of straightening a shelf of out-of-order books. You could compare and swap neighbours over and over (slow), or pick one book as a reference, push everything shorter to its left and taller to its right, then repeat on each side. That “pick a pivot and split” habit is exactly quicksort, and it settles every book into place in about n log n moves.

One Big Idea

Any correct sort works, but the interview favorite is quicksort: pick a pivot, then partition the array so smaller elements sit left of the pivot and larger ones sit right. The pivot is now in its final position; recursively sort the two sides. It sorts in place in O(n log n) on average.

Intuition

This problem is quite open-ended because there are many sorting algorithms we could use to sort an array, each with its own pros and cons. In this explanation, we focus on the quicksort algorithm, which runs in O(nlog(n))O(n\log(n)) time on average, where nn denotes the length of the array.

Quicksort
Conceptually, the goal of quicksort is to sort the array by placing each number in its sorted position one at a time. To correctly position a number, we move all numbers smaller than it to its left, and all numbers larger than it to its right. At each step, we call this number the pivot. We discuss how a pivot is selected later in the explanation.

Image represents a visual depiction of a partitioning step within a sorting algorithm, likely Quicksort.  The top row shows an unsorted array [6, 8, 4, 2, 7, 3, 1, 5], with the element '5' highlighted in an orange box and labeled 'pivot'. A downward arrow indicates the partitioning process. The bottom row displays the array after partitioning.  The numbers less than the pivot (5) are grouped in a light-blue box labeled 'less than 5' as [4, 2, 3, 1]. The pivot (5) is shown in an orange box, positioned between the two groups, and labeled 'in the correct position'. The numbers greater than the pivot are grouped in a light-purple box labeled 'greater than 5' as [6, 8, 7]. This illustrates how the pivot element is placed in its final sorted position, with all smaller elements to its left and all larger elements to its right, a key step in the Quicksort algorithm.

This process is called partitioning because we’re dividing the array into two parts around the pivot:

  1. The left part with elements smaller than the pivot.
  2. The right part with elements larger than the pivot.

Note that neither the left nor the right part of the pivot value need to be sorted. All that matters is that the pivot is in the correct position.

After partitioning, we just need to sort the left and right parts. We can do this by recursively calling quicksort on both of them. This makes quicksort a divide and conquer strategy. The pseudocode for this is provided below:

def quicksort(nums, left, right):
    # Partition the array and obtain the index of the pivot.
    pivot_index = partition(nums, left, right)
    # Sort the left and right parts.
    quicksort(nums, left, pivot_index - 1)
    quicksort(nums, pivot_index + 1, right)

Let’s discuss the partitioning process in more detail.

Partitioning
There are two primary steps to partitioning:

  1. Selecting the pivot.
  2. Rearranging the elements so elements smaller than the pivot are on its left, and elements larger than the pivot are on its right.

Selecting the pivot
We can actually choose any number as the pivot. The method of selecting a pivot can be optimized, which is discussed later, but for simplicity, we can choose the rightmost number as the pivot:

Image represents a sequence of numbers, [6, 8, 4, 2, 7, 3, 1, 5], displayed horizontally.  Each number is positioned above its index, indicated in grey text below (0 through 7). The number 5, located at index 7, is highlighted within an orange rectangular box, labeled 'pivot' in orange text to its upper right.  The arrangement visually depicts a data array, with the highlighted '5' signifying a pivot element within the context of a sorting algorithm or similar data structure operation.  No URLs or parameters are present; the image solely illustrates the positional relationship between the numbers and the designated pivot.

Rearranging elements around the pivot
Let’s see if we can do this in linear time without using extra space.

If we can ensure all numbers less than the pivot are placed to the left, then numbers greater than or equal to the pivot will consequently be placed to the right. This can be done using two pointers:

The main idea here is that whenever we encounter a number less than the pivot, swap it with nums[lo].


Let's look at the example below to understand how this works. First, keep advancing i until we find a number less than the pivot:

Image represents a visual depiction of a portion of a sorting algorithm, likely Quicksort.  The diagram shows an array `[6, 8, 4, 2, 7, 3, 1, 5]` with indices 0 through 7 displayed below each element.  Above the array, two boxes labeled '10' (light gray) and 'i' (black) indicate variables. Arrows point from both '10' and 'i' downwards, suggesting these variables are used to iterate through the array.  To the right, a separate box labeled 'pivot' contains the value '5', indicating the pivot element for this iteration. A dashed-line box depicts a conditional statement: 'nums[i] > pivot'.  An arrow extends from this condition to the word 'continue,' illustrating that if the condition `nums[i] > pivot` is true, the algorithm continues to the next iteration.  The overall diagram illustrates a single step in the partitioning phase of a sorting algorithm, where elements are compared to the pivot to determine their position in the sorted array.
Image represents a snapshot of a sorting algorithm, likely Quicksort, illustrating a single iteration.  A numerical array `[6, 8, 4, 2, 7, 3, 1, 5]` is shown with indices 0 through 7 displayed below each element.  A grey box labeled 'lo' (likely representing the lower index of a partition) points to the element '6' at index 0, and a black box labeled 'i' (likely representing the current index) points to the element '8' at index 1. A dashed arrow connects 'lo' to 'i', suggesting a comparison or traversal between these indices.  A separate grey box labeled 'pivot' contains the value '5', indicating the pivot element selected for this partition.  A decision box with dashed borders shows a condition: `nums[i] > pivot`.  If this condition is true (as it is for '8'), a solid arrow points to the word 'continue', implying the algorithm proceeds to the next iteration, potentially incrementing 'i' to compare the next element.  The overall diagram visualizes the state of the algorithm during a comparison step within the partitioning phase of a sorting algorithm.
Image represents a visual depiction of a step in a sorting algorithm, likely Quicksort.  A numerical array `[6, 8, 4, 2, 7, 3, 1]` is shown, with indices 0 through 6 displayed below each element.  Above the array, two boxes labeled '10' (grey) and 'i' (black) indicate variables. A downward arrow from '10' points to the element '6' at index 0, suggesting '10' might represent the array's length or a related parameter.  A downward arrow from 'i' points to the element '4' at index 2. A dashed curved arrow connects 'i' to '8' at index 1, indicating a comparison or swap operation.  Finally, a separate box labeled 'pivot' contains the value '5', suggesting '5' is the pivot element selected for this partitioning step in the algorithm.  The arrangement visually demonstrates the algorithm's progress in partitioning the array around the pivot.

Once a number less than the pivot is found, move it to the left by swapping it with nums[lo]. Then, increment lo so it points to where the next number less than the pivot should be placed:

Image represents a visual depiction of a step within a sorting algorithm, likely Quicksort.  The left side shows an array `[6, 8, 4, 2, 7, 3, 1, 5]` with indices 0 through 7 displayed below each element.  Arrows point downwards from labeled boxes 'lo' (initially pointing to index 0) and 'i' (initially pointing to index 2) indicating their current positions within the array. The number 5 is labeled as 'pivot'. The right side shows a decision box with dashed borders.  This box contains a condition `nums[i] < pivot`, which checks if the element at index `i` (currently 4) is less than the pivot (5). If true, two actions are performed:  `swap(nums[i], nums[lo])` swaps the element at index `i` with the element at index `lo`, and `lo += 1` increments the `lo` index.  The arrows from the decision box represent the flow of control based on the condition's outcome.  The overall diagram illustrates a single iteration of the partitioning phase in a sorting algorithm, where elements smaller than the pivot are moved to the left side of the array.
Image represents a step in a sorting algorithm, likely Quicksort, illustrating a swap operation.  The array `[4, 8, 6, 2, 7, 3, 1]` is shown with indices 0 through 6 displayed below each element in gray.  A light-blue box labeled 'lo' points down to the element at index 0 (value 4), and a black box labeled 'i' points down to the element at index 2 (value 6). A curved light-blue arrow labeled 'swapped' connects the element at index 0 (4) to the element at index 2 (6), indicating these two elements have been exchanged.  To the right, a gray box labeled 'pivot' contains the value 5, which is likely the pivot element around which the partition is being built.  The arrangement visually depicts the state of the array during the partitioning phase of the algorithm, where elements smaller than the pivot are moved to the left and larger elements to the right.
Image represents a visual depiction of a step in a sorting algorithm, likely Quicksort.  An array [4, 8, 6, 2, 7, 3, 1] is shown with indices 0 through 6 displayed below each element. A light-blue box labeled 'lo' (likely representing the low index) points to the element '8' at index 1 via a dashed arrow indicating a potential swap or comparison. A black box labeled 'i' (likely representing the iterator or current index) points to the element '6' at index 2 with a solid arrow.  A grey box labeled 'pivot' contains the value '5' at index 7, indicating this element is the pivot around which the partition is being formed. The arrangement shows the algorithm's progress in partitioning the array based on the pivot value.  The dashed arrow from 'lo' suggests a comparison or movement of the '8' element relative to the pivot. The solid arrow from 'i' indicates the iterator's position in the array during the partitioning process.

Continue this process until all numbers less than the pivot are on the left of the array:

The image represents a visual depiction of a pivot element in an array during a sorting algorithm, likely Quicksort.  A numerical array `[4, 2, 3, 1, 8, 6, 7, 5]` is shown, with indices 0 through 7 displayed below each element.  Elements 4, 2, and 3 are colored light blue, indicating they might be processed or sorted already.  The number 1 is also light blue, suggesting it's part of a sorted or processed section. The number 8, 6, and 7 are in black, implying they are yet to be processed.  A light gray box labeled 'pivot' contains the number 5, located at index 7, signifying it's the chosen pivot element for this iteration.  A light blue box above the array contains the number '10,' which might represent the total number of elements or a related parameter. A downward-pointing arrow connects the '10' box to the number 8 in the array, possibly indicating the pivot selection process or the current position of the algorithm's pointer.

The last step is to move the pivot to the correct position by swapping the pivot with nums[lo]:

Image represents a visual depiction of a single step in a sorting algorithm, likely Quicksort.  The image shows an array [4, 2, 3, 1, 5, 6, 7, 8] with indices 0 through 7 displayed below each element. The number 5 is highlighted in a light beige box with an orange border and labeled 'pivot'.  A curved orange arrow connects the element 1 (at index 3) and the element 8 (at index 7), labeled 'swapped,' indicating that these two elements have been exchanged during the partitioning phase of the algorithm.  The arrangement visually demonstrates the in-place swapping of elements relative to the pivot element during the partitioning process of a sorting algorithm.

Implementation

from typing import List
    
def sort_array(nums: List[int]) -> List[int]:
    quicksort(nums, 0, len(nums) - 1)
    return nums
    
def quicksort(nums: List[int], left: int, right: int) -> None:
    # Base case: if the subarray has 0 or 1 element, it's already sorted.
    if left >= right:
        return
    # Partition the array and retrieve the pivot index.
    pivot_index = partition(nums, left, right)
    # Call quicksort on the left and right parts to recursively sort them.
    quicksort(nums, left, pivot_index - 1)
    quicksort(nums, pivot_index + 1, right)
    
def partition(nums: List[int], left: int, right: int) -> int:
    pivot = nums[right]
    lo = left
    # Move all numbers less than the pivot to the left, which consequently positions
    # all numbers greater than or equal to the pivot to the right.
    for i in range(left, right):
        if nums[i] < pivot:
            nums[lo], nums[i] = nums[i], nums[lo]
            lo += 1
    # After partitioning, 'lo' will be positioned where the pivot should be. So, swap
    # the pivot number with the number at the 'lo' pointer.
    nums[lo], nums[right] = nums[right], nums[lo]
    return lo
int partitionArray(vector<int>& nums, int left, int right) {
    int pivot = nums[right];                 // choose the rightmost as pivot
    int lo = left;                           // boundary for "smaller than pivot"
    for (int i = left; i < right; i++) {
        if (nums[i] < pivot) {
            int tmp = nums[lo];
            nums[lo] = nums[i];
            nums[i] = tmp;
            lo++;
        }
    }
    // Put the pivot in its final sorted position.
    int tmp = nums[lo];
    nums[lo] = nums[right];
    nums[right] = tmp;
    return lo;
}

void quicksort(vector<int>& nums, int left, int right) {
    // Base case: 0 or 1 element is already sorted.
    if (left >= right) {
        return;
    }
    int pivotIndex = partitionArray(nums, left, right);
    quicksort(nums, left, pivotIndex - 1);   // sort the left part
    quicksort(nums, pivotIndex + 1, right);  // sort the right part
}

vector<int> sortArray(vector<int> nums) {
    quicksort(nums, 0, (int)nums.size() - 1);
    return nums;
}
int[] sortArray(int[] nums) {
    quicksort(nums, 0, nums.length - 1);
    return nums;
}

void quicksort(int[] nums, int left, int right) {
    // Base case: 0 or 1 element is already sorted.
    if (left >= right) {
        return;
    }
    int pivotIndex = partitionArray(nums, left, right);
    quicksort(nums, left, pivotIndex - 1);   // sort the left part
    quicksort(nums, pivotIndex + 1, right);  // sort the right part
}

int partitionArray(int[] nums, int left, int right) {
    int pivot = nums[right];                 // choose the rightmost as pivot
    int lo = left;                           // boundary for "smaller than pivot"
    for (int i = left; i < right; i++) {
        if (nums[i] < pivot) {
            int tmp = nums[lo];
            nums[lo] = nums[i];
            nums[i] = tmp;
            lo++;
        }
    }
    // Put the pivot in its final sorted position.
    int tmp = nums[lo];
    nums[lo] = nums[right];
    nums[right] = tmp;
    return lo;
}

Complexity Analysis

Time complexity: The time complexity of sort_array can be analyzed in terms of the average and worst cases:

Space complexity: The space complexity can also be analyzed in terms of the average and worst cases:

Optimization

As mentioned in the above complexity analysis, it’s possible for a worst-case time complexity of O(n2)O(n^2) to occur. Let’s dive into when this can happen. Consider the following array, which is already sorted:

Image represents a simple array or list data structure depicted using square brackets `[]` to enclose five integer elements: 1, 2, 3, 4, and 5.  These elements are arranged sequentially within the brackets, separated by spaces, indicating their order within the data structure. No connections or information flow is explicitly shown beyond the inherent sequential arrangement of the elements within the array.  The image solely illustrates the basic structure of a list containing five integers, with no further metadata, labels, or external connections depicted.

If we perform quicksort on this array, we choose the rightmost element as the pivot and partition the other elements around this pivot. However, since this pivot is the largest element, there will be n-1 elements less than the pivot and 0 elements greater than or equal to it:

Image represents a visual depiction of a partitioning step within a sorting algorithm, likely Quicksort.  The illustration shows an array represented as `[1 2 3 4 5]`. The number 5 is highlighted in an orange box and labeled 'pivot'.  A light-blue rounded rectangle encloses the numbers 1, 2, 3, and 4, labeled 'left part' in light-blue text. A purple arrow originates from the right side of the pivot (5) and points to the right, indicating the absence of a 'right part' in this specific step.  The overall arrangement demonstrates the partitioning process where elements smaller than the pivot are placed to its left, and in this case, no elements are larger than the pivot, resulting in an empty right partition.

This creates quite an uneven partition. When we call quicksort on the left part, the same imbalance will occur, but with a left part consisting of n-2 elements:

Image represents a visual depiction of a partitioning step in a sorting algorithm, likely Quicksort.  The image shows a numerical array `[1, 2, 3, 4]` enclosed in square brackets. The first three elements, `[1, 2, 3]`, are grouped together in a light-blue rounded rectangle and labeled in light-blue text as 'left part'. The last element, `4`, is separately enclosed in an orange rounded rectangle and labeled above in orange text as 'pivot'. A purple arrow originates from the bottom of the 'pivot' rectangle and points to the right, indicating the direction of data flow, and is labeled in purple text as 'no right part'. This illustrates a scenario where the pivot element is already in its correct sorted position, resulting in an empty right partition after the partitioning step.  The arrangement visually separates the array into a 'left part' and an implicitly empty 'right part' based on the pivot's position.

Continuing this until the quicksort process is complete results in a recursion depth of n.

An uneven partition occurs when we choose an extreme pivot: one that’s larger or smaller than most other elements. Consistently picking an extreme pivot can occur when the array is sorted in increasing or decreasing order, or when there are many duplicates in the array.

To reduce the likelihood of choosing an extreme pivot, we can modify quicksort to choose a random pivot instead. There still remains a small chance of consistently picking an extreme pivot, but this outcome is no longer dependent on the order of the input array. A more detailed answer is discussed in the provided reference [1].

We can integrate this change into our current solution by randomly selecting an index and swapping its element with the rightmost element, before performing the partition. This way, we don't have to modify the partition function, as it uses the rightmost element:

def quicksort_optimized(nums: List[int], left: int, right: int) -> None:
    if left >= right:
        return
    # Choose a pivot at a random index.
    random_index = random.randint(left, right)
    # Swap the randomly chosen pivot with the rightmost element to position the pivot
    # at the rightmost index.
    nums[random_index], nums[right] = nums[right], nums[random_index]
    pivot_index = partition(nums, left, right)
    quicksort_optimized(nums, left, pivot_index - 1)
    quicksort_optimized(nums, pivot_index + 1, right)

Interview Follow-Up

Let’s say the interviewer introduces the following constraints to the initial sorting problem:

Does our approach to the problem change, and should we still use quicksort? Considering that all values in our array now fall within the limited range of [0, 10310^3], a counting sort approach becomes appropriate.

Counting sort
Counting sort is a non-comparison-based sorting algorithm that works by counting the number of occurrences of each element in the array, then using these counts to place each element in its correct sorted position:

Image represents a visual explanation of a counting sort algorithm.  At the top, a list named `nums` is shown, containing the integer values [2, 1, 0, 0, 2, 4, 2]. A gray line underlines this list. Below, a list called `counts` is displayed, representing the frequency of each element in `nums`.  `counts` contains [2, 1, 3, 0, 1], indicating two 0s, one 1, three 2s, zero 3s, and one 4.  Small gray numbers (0, 1, 2, 3, 4) are placed above `counts` to clarify the index corresponding to each element's count. Orange curved arrows connect each element in `counts` to the corresponding number of elements in the final `res` list.  `res` is the sorted output list, shown at the bottom as [0, 0, 1, 2, 2, 2, 4], where each element's position reflects its sorted order based on the counts in the `counts` list.  The arrows visually demonstrate how the frequency information in `counts` is used to populate the sorted `res` list.

We can do this in two steps:

  1. Count occurrences: create a counts array, where each of its indexes represents an element from the original array. Increment the value at each index based on how many times the corresponding element appears in the original array.

  2. Build sorted array (res): iterate through each index of the counts array and add that index (i) to the sorted array as many times as its value (counts[i]) indicates.

Counting sort is efficient here because we know the largest possible number in the array is at most 10310^3, which means our counts array will have a maximum size of 10310^3 + 1. However, if this problem constraint is not specified and the maximum value in the array may be very large, then a counting sort solution might not be appropriate, due to the potentially large size of the counts array.

Implementation

Note that there’s another common method for implementing counting sort, which is detailed in the reference provided [2].

from typing import List
    
def sort_array_counting_sort(nums: List[int]) -> List[int]:
    if not nums:
        return []
    res = []
    # Count occurrences of each element in 'nums'.
    counts = [0] * (max(nums) + 1)
    for num in nums:
        counts[num] += 1
    # Build the sorted array by appending each index 'i' to it a total of 'counts[i]'
    # times.
    for i, count in enumerate(counts):
        res.extend([i] * count)
    return res

Complexity Analysis

Time complexity: The time complexity of sort_array_counting_sort is O(n+k)O(n+k), where kk denotes the maximum value of nums. This is because it takes O(n)O(n) time to count the occurrences of each element and O(k)O(k) time to build the sorted array.

Space complexity: The space complexity is O(n+k)O(n+k), since the res array occupies O(n)O(n) space, and the counts array takes up O(k)O(k) space. Note that res is considered in the space complexity, as counting sort is not an in-place sorting algorithm requiring an additional array to store the sorted result.

Dry Runs

Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.

✅ Dry run — partition [3, 7, 4, 2, 5], pivot = 5.
lo marks where the next smaller-than-pivot element goes.
inums[i]< 5?action → array, lo
03yesswap(0,0) → [3,7,4,2,5], lo=1
17no— → lo=1
24yesswap(1,2) → [3,4,7,2,5], lo=2
32yesswap(2,3) → [3,4,2,7,5], lo=3
endpivotswap(lo=3, right=4) → [3,4,2,5,7]

Returns pivot index 3. Left part [3, 4, 2] and right part [7] are sorted recursively. ✓

❌ Dry run (edge) — already sorted [1, 2, 3] with a fixed rightmost pivot.

Pivot 3 is already largest → nothing swaps, pivot stays at the end, and we recurse on [1, 2]. Each partition peels off just one element, so a fixed pivot degrades to O(n2) here. The fix is a random pivot (below).

Interview Tip

Tip: Quicksort is useful for in-place sorting.
While quicksort isn’t a stable sorting algorithm, it is an in-place sorting algorithm, meaning it requires less additional space compared to an algorithm such as merge sort, which takes O(n)O(n) space when used to sort an array.