Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

Quick Sort: Pivoting and Performance

Recommended resources

Hello! In our previous lesson, we explored Merge Sort, a reliable sorting algorithm with a consistent performance, achieved through the divide-and-conquer strategy. We noted its main trade-off: it requires extra space for the merging process.

Today, we'll continue our exploration of divide-and-conquer algorithms with Quick Sort. You'll discover that it also boasts an average time complexity of but with a significant advantage: it can sort an array "in-place," typically using only extra space on the recursion stack. However, this efficiency comes with a catch—its performance is highly dependent on a key choice we make at each step.

By the end of this lesson, you will be able to implement Quick Sort in Java, understand the critical role of the "pivot" element, and analyze the stark difference between its average-case and worst-case performance scenarios. This is another fundamental algorithm frequently discussed in technical interviews.

1. The Core Idea: Pick a Pivot and Partition

Like Merge Sort, Quick Sort uses the divide-and-conquer approach. However, it shifts the main "work" to the "divide" step, not the "combine" step. The process is as follows:

  1. Pick a Pivot: Choose one element from the array. This element is called the pivot. The strategy for choosing the pivot is crucial, and we'll discuss it in detail.
  2. Partition: Rearrange the array so that all elements smaller than the pivot are to its left, and all elements greater than the pivot are to its right. After this step, the pivot element is in its final, correct sorted position.
  3. Recurse: Recursively apply the Quick Sort algorithm to the two sub-arrays: the one to the left of the pivot and the one to the right.

The key insight is that once the pivot is in its correct place, it never needs to be moved again. The problem is now reduced to sorting the two smaller, independent sub-arrays.

Let's watch Striver's explanation. He masterfully breaks down this core intuition.

Quick Sort For Beginners | Strivers A2Z DSA Course

This video introduces the fundamental concept of Quick Sort. Striver explains the logic of selecting a pivot, placing it correctly, and ensuring smaller elements are on the left and larger ones are on the right.

Watch from the beginning to understand the core strategy of the algorithm. Focus on the three main steps: Pick a pivot. Place the pivot in its correct sorted position. Ensure elements smaller than the pivot are on its left and larger elements are on its right.


2. The Partition Function: The Heart of Quick Sort

The most intricate part of Quick Sort is the partition function. Its job is to take an array (or sub-array) and a chosen pivot, and perform the rearrangement we just described. A common and efficient way to do this in-place is by using two pointers.

Striver's preferred method, which aligns with your course structure, involves:

  1. Choosing the first element (arr[low]) as the pivot.
  2. Initializing two pointers, i at low and j at high.
  3. Moving i forward until you find an element > pivot.
  4. Moving j backward until you find an element <= pivot.
  5. If i is still to the left of j, swap arr[i] and arr[j].
  6. Repeat until the pointers i and j cross.
  7. Once they cross, swap the pivot (arr[low]) with arr[j]. The pivot is now at its correct sorted position, and the function returns this index (j).

Let's see the pseudocode and a dry run of this partitioning logic. This will clarify how the array is rearranged without using extra memory.

Quick Sort For Beginners | Strivers A2Z DSA Course

This part of the video is crucial. It translates the abstract idea of partitioning into a concrete, implementable algorithm using two pointers.

Watch the detailed explanation of the partitioning logic. Pay close attention to how the i and j pointers move and when the swaps occur. Following this dry run carefully is key to understanding the implementation.


3. Implementation in Java

With the partition logic clear, we can assemble the full Quick Sort algorithm. It consists of two main parts:

  • quickSort(arr, low, high): The recursive function that checks the base case (low >= high) and orchestrates the process. It calls partition to get the pivot's final index and then recursively calls itself for the left and right sub-arrays.
  • partition(arr, low, high): The function we just studied, which rearranges the sub-array and returns the pivot's index.

The following resource from takeuforward.org provides a clean Java implementation that directly reflects the logic from Striver's video.

Quick Sort Algorithm - Tutorial - takeuforward

This article presents the complete Java code for Quick Sort, including both the recursive quickSort driver method and the partition helper method.

Please review the complete Java code. Notice how the quickSort function makes two recursive calls using the pivotIndex returned by the partition function. Compare this code to the pseudocode you saw in the video to solidify your understanding.


4. Performance: Average vs. Worst Case

Here is where Quick Sort reveals its complex personality. Unlike Merge Sort, its performance is not a constant guarantee.

Average-Case Performance:

The ideal scenario for Quick Sort is when the pivot choice consistently divides the array into two roughly equal halves. If the pivot is always the median element, each partition creates two sub-problems of size n/2.

  • Partitioning Work: The partition function scans the sub-array, which takes linear time, .
  • Recursion Depth: Halving the problem at each step results in a recursion depth of .

This leads to the same recurrence relation as Merge Sort, , which resolves to . Even if the splits are not perfectly balanced (e.g., a 10%-90% split), the time complexity remains . This efficient performance is what we observe on average with a good pivot strategy.

A recursion tree for Quick Sort in the average or best case. The problem is divided into balanced subproblems, leading to a tree height of O(log n). With O(n) work per level, the total time is O(n log n).

Worst-Case Performance:

The worst-case scenario occurs when the chosen pivot is consistently the smallest or largest element in the sub-array. This leads to extremely unbalanced partitions. A common trigger for this is trying to sort an already sorted (or reverse-sorted) array while always picking the first or last element as the pivot.

In this case, the partition step splits the array of size k into two sub-arrays of sizes 0 and k-1.

  • Partitioning Work: Still takes linear time, .
  • Recursion Depth: The problem size only decreases by one at each step, leading to a recursion depth of .

The total work becomes a sum of an arithmetic series: , which is .

A recursion tree for Quick Sort in the worst case. The pivot choice leads to highly unbalanced subproblems (n-1 and 0), resulting in a skewed tree with a depth of O(n). This causes the total time complexity to become O(n^2).

This performance disparity highlights the importance of the pivot selection strategy. While picking the first or last element is simple to code, it's vulnerable. A common and more robust technique is to pick a random pivot. This makes the worst-case scenario statistically very unlikely, as it's improbable to randomly pick the worst pivot every single time. Another popular method is the "median-of-three" strategy, where you take the first, middle, and last elements and choose their median as the pivot.

For a formal analysis, the Baeldung article provides a concise explanation.

Quicksort Algorithm Implementation in Java | Baeldung

This article section breaks down the time complexity for the best, worst, and average cases, clearly explaining why they differ.

Read the section on Time Complexity. This will reinforce your understanding of why the quality of the partition dictates the overall performance.

Space Complexity

Quick Sort's main advantage over Merge Sort is its space efficiency.

  • It operates in-place, so it doesn't require an auxiliary array.
  • The space it uses comes from the recursion call stack.
    • Average Case: The recursion depth is , so the space complexity is .
    • Worst Case: The recursion depth is , leading to a space complexity of .

This makes Quick Sort a preferred choice when memory is a concern, as its average-case space usage is excellent.


Conclusion

In this lesson, we've dissected Quick Sort, a powerful but variable sorting algorithm. It exemplifies a different flavor of divide-and-conquer where the intelligence lies in the partitioning step.

Key Takeaways:

  • Core Logic: Quick Sort works by picking a pivot, partitioning the array around it, and then recursively sorting the two resulting sub-arrays.
  • In-Place Sorting: Its primary advantage is sorting in-place, giving it an average space complexity of .
  • Pivot is Key: The choice of pivot is critical. A good pivot leads to balanced partitions and time complexity.
  • Worst-Case: A poor pivot strategy (e.g., on an already sorted array) can lead to highly unbalanced partitions and degrade performance to . Randomizing the pivot is a standard way to mitigate this risk.

We have now covered the most important "from-scratch" sorting algorithms. In our next lesson, we will shift our focus to practical application. You will learn how to use Java's built-in sorting methods, Arrays.sort() and Collections.sort(), and, importantly, how to customize their behavior for complex objects using custom Comparators. This is an essential skill for real-world programming and competitive coding.