Quick Sort
A divide-and-conquer algorithm that picks a pivot element and partitions the array around it, then recursively sorts the sub-arrays.
Algorithm Information
Time Complexity
O(n log n)O(n log n)O(n²)Space Complexity
O(log n)Properties
Algorithm Code
1function quickSort(arr, low = 0, high = arr.length - 1) {2 if (low < high) {3 // Partition the array and get pivot index4 const pivotIndex = partition(arr, low, high);5 6 // Recursively sort elements before and after partition7 quickSort(arr, low, pivotIndex - 1);8 quickSort(arr, pivotIndex + 1, high);9 }10 return arr;11}12 13function partition(arr, low, high) {14 // Choose rightmost element as pivot15 const pivot = arr[high];16 let i = low - 1; // Index of smaller element17 18 for (let j = low; j < high; j++) {19 // If current element is smaller than or equal to pivot20 if (arr[j] <= pivot) {21 i++;22 [arr[i], arr[j]] = [arr[j], arr[i]]; // Swap elements23 }24 }25 26 // Place pivot in correct position27 [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];28 return i + 1; // Return pivot index29}Variable State
Call Stack
Array Visualization
Watch how Quick Sort partitions the array around pivot elements and recursively sorts sub-arrays
Statistics
Legend
Starting Quick Sort algorithm
How Quick Sort Works
Discover the power of partitioning in this efficient divide-and-conquer algorithm with average O(n log n) performance.
Algorithm Overview
Quick Sort is a highly efficient divide-and-conquer sorting algorithm that works by selecting a 'pivot' element and partitioning the array around it. Elements smaller than the pivot go to the left, larger elements go to the right, then the process is recursively applied to the subarrays.
🎯 Partitioning Strategy
The key insight is the partition operation: after partitioning around a pivot, the pivot is in its final sorted position, and we never need to move it again. This creates two independent subproblems that can be solved recursively, leading to excellent average-case performance.
Step-by-Step Process
Choose Pivot
Select a pivot element from the array. Common strategies include choosing the first, last, middle, or a random element. The choice affects performance but not correctness.
const pivot = arr[high];
Partition Array
Rearrange the array so all elements smaller than the pivot come before it, and all elements greater come after it. The pivot is now in its final position.
for (let j = low; j < high; j++) {
if (arr[j] < pivot) {
swap(arr[++i], arr[j]);
}
}
Place Pivot
Swap the pivot with the element at the partition boundary. The pivot is now in its correct final position in the sorted array.
Recursive Calls
Recursively apply quick sort to the left subarray (elements smaller than pivot) and right subarray (elements larger than pivot).
quickSort(arr, partitionIndex + 1, high);
Partitioning in Action
Here's how the partition operation works with an example:
🔄 Partition Invariant
During partitioning, we maintain the invariant: elements at indices [low...i] are ≤ pivot, elements at indices [i+1...j-1] are > pivot, and elements at indices [j...high-1] are unprocessed.
Complete Implementation
Here's the complete quick sort implementation with Lomuto partition scheme:
function quickSort(arr, low = 0, high = arr.length - 1) {
if (low < high) {
// Partition the array and get pivot index
const pivotIndex = partition(arr, low, high);
// Recursively sort elements before and after partition
quickSort(arr, low, pivotIndex - 1); // Left subarray
quickSort(arr, pivotIndex + 1, high); // Right subarray
}
}
function partition(arr, low, high) {
// Choose the rightmost element as pivot
const pivot = arr[high];
// Index of smaller element (indicates right position of pivot)
let i = low - 1;
for (let j = low; j < high; j++) {
// If current element is smaller than or equal to pivot
if (arr[j] <= pivot) {
i++; // Increment index of smaller element
[arr[i], arr[j]] = [arr[j], arr[i]]; // Swap elements
}
}
// Place pivot in correct position
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1; // Return position of pivot
}
// Alternative: Hoare Partition Scheme (more efficient)
function hoarePartition(arr, low, high) {
const pivot = arr[low]; // Choose first element as pivot
let i = low - 1;
let j = high + 1;
while (true) {
do { i++; } while (arr[i] < pivot);
do { j--; } while (arr[j] > pivot);
if (i >= j) return j;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// Example usage:
const numbers = [64, 34, 25, 12, 22, 11, 90];
console.log("Original:", numbers);
quickSort(numbers);
console.log("Sorted:", numbers);Key Concepts & Characteristics
✅ Advantages
- •Fast average case: O(n log n) on average, often faster than merge sort
- •In-place sorting: Only requires O(log n) extra space for recursion
- •Cache-friendly: Good spatial locality of reference
- •Practical performance: Low constant factors make it very fast
- •Tail recursion: Can be optimized to reduce stack usage
❌ Disadvantages
- •Worst-case O(n²): Poor pivot choices lead to quadratic time
- •Unstable: Equal elements may change relative order
- •Sensitive to input: Performance depends on pivot selection
- •Not adaptive: Doesn't benefit from partially sorted data
- •Deep recursion: Can cause stack overflow on worst-case input
⚠️ Worst-Case Scenario
Quick sort's worst case occurs when the pivot is always the smallest or largest element, creating highly unbalanced partitions. This happens with already sorted arrays when using first/last element as pivot, leading to O(n²) time complexity and O(n) space complexity.
Complexity Analysis
Best Case
Pivot always divides array into two equal halves, creating balanced recursion tree.
Average Case
Random pivot selection leads to reasonably balanced partitions on average.
Worst Case
Pivot is always minimum or maximum, creating unbalanced partitions.
Mathematical Analysis
Pivot Selection Strategies
Common Strategies
- •First Element: Simple but poor for sorted dataTime: O(1), Risk: High for sorted input
- •Last Element: Most common, same issues as firstTime: O(1), Risk: High for sorted input
- •Random Element: Good average performanceTime: O(1), Risk: Low, probabilistic guarantee
- •Median-of-Three: Choose median of first, middle, lastTime: O(1), Risk: Lower, better practical performance
Advanced Techniques
- •Median-of-Medians: Guaranteed good pivotTime: O(n), Guarantee: Worst-case O(n log n)
- •Introsort: Switch to heapsort if recursion too deepUsed in C++ std::sort, guarantees O(n log n)
- •Dual-Pivot: Use two pivots for 3-way partitioningUsed in Java's Arrays.sort(), better for duplicates
When to Use Quick Sort
✅ Excellent for:
- • General-purpose sorting: Great all-around performance
- • Large datasets: Excellent average-case performance
- • Memory-constrained systems: In-place with low space overhead
- • Cache-sensitive applications: Good spatial locality
- • Random data: Performs very well on random inputs
- • Systems programming: Used in many standard libraries
❌ Consider alternatives for:
- • Guaranteed performance: Use merge sort or heap sort
- • Stable sorting required: Use merge sort or stable variants
- • Nearly sorted data: Use insertion sort or adaptive algorithms
- • Adversarial input: Risk of worst-case O(n²) performance
- • Small datasets: Insertion sort may be faster
🏆 Industry Standard
Quick sort (with optimizations) is used in C's qsort(), C++'s std::sort(Introsort), and Java's Arrays.sort() for primitive types. Its combination of excellent average performance, in-place operation, and cache efficiency makes it the go-to choice for most sorting needs.
Common Optimizations
Performance Optimizations
- • Insertion sort for small subarrays: Switch when size < 10-20
- • Tail recursion elimination: Reduce stack usage
- • Iterative implementation: Avoid recursion overhead
- • Three-way partitioning: Handle many duplicate keys efficiently
Robustness Improvements
- • Randomized pivot: Avoid worst-case on sorted input
- • Median-of-three: Better pivot selection
- • Introsort hybrid: Fall back to heap sort if too deep
- • Dual-pivot quicksort: Better performance on modern hardware
💡 Pro Tip: Hybrid Approach
Modern implementations often combine multiple techniques: use quicksort for large subarrays, insertion sort for small ones, and heap sort as a fallback for deep recursion. This gives you the best of all worlds: fast average case, good worst case, and excellent small-array performance.