Quick Sort Explained
Quick sort is a divide-and-conquer algorithm that sorts by picking a pivot element, partitioning the array so that smaller values go to the pivot's left and larger ones to its right, then recursively sorting each side. On average it runs in O(n log n) and sorts in place with very little extra memory, which makes it one of the fastest general-purpose sorts in practice — though a poorly chosen pivot can drag it down to O(n2).
The partition idea
The clever part of quick sort is partitioning. You choose a pivot, then rearrange the array so that everything less than the pivot sits before it and everything greater sits after it. After this single pass, the pivot is in its final sorted position — it will never move again. You then apply the same process to the left and right segments independently.
Because each partition correctly places one element and splits the rest into two smaller problems, repeated partitioning sorts the whole array.
Quick sort in C++
#include <iostream>
#include <vector>
using namespace std;
// Partition arr[low..high] around a pivot (the last element).
// Returns the pivot's final index. This pass is O(n) for the range.
int partition(vector<int>& arr, int low, int high) {
int pivot = arr[high]; // choose the last element as pivot
int i = low - 1; // boundary of the "smaller than pivot" region
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(arr[i], arr[j]); // move smaller element into the left region
}
}
swap(arr[i + 1], arr[high]); // place pivot right after the smaller region
return i + 1; // pivot is now in its final position
}
// Recursively sort arr[low..high]. Average O(n log n), worst case O(n^2).
void quickSort(vector<int>& arr, int low, int high) {
if (low < high) {
int p = partition(arr, low, high); // pivot lands at index p
quickSort(arr, low, p - 1); // sort the left part
quickSort(arr, p + 1, high); // sort the right part
}
}
int main() {
vector<int> arr = {5, 2, 9, 1, 6, 3};
quickSort(arr, 0, arr.size() - 1);
for (int x : arr) cout << x << " "; // 1 2 3 5 6 9
return 0;
}
Notice there is no temporary array — partitioning rearranges elements within the original array, which is why quick sort is in place.
Average versus worst case
When the pivot splits the array into two roughly equal halves, the recursion is log n deep and each level does O(n) partitioning work, giving the O(n log n) average. But if the pivot is always the smallest or largest element — which happens with the last-element pivot on already-sorted data — the partitions are maximally unbalanced (one element versus the rest), the recursion becomes n deep, and the cost balloons to O(n2).
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesAvoiding the worst case
The fix is choosing a better pivot:
- Randomised pivot — pick a random element, making the worst case astronomically unlikely for any fixed input.
- Median-of-three — pick the median of the first, middle, and last elements, which handles sorted and reverse-sorted inputs well.
These strategies keep quick sort fast on real-world data. This is the key difference from merge sort, which guarantees O(n log n) but always uses O(n) extra space.
Quick sort versus merge sort
| Property | Quick sort | Merge sort |
|---|---|---|
| Average time | O(n log n) | O(n log n) |
| Worst case | O(n2) | O(n log n) |
| Space | O(log n) (recursion) | O(n) |
| In place | Yes | No |
| Stable | Not by default | Yes |
Quick sort is usually faster in practice due to good cache behaviour and low overhead, and it is the common default for in-memory array sorting. Choose merge sort when you need the guaranteed worst case or stability.
Common mistakes
- Always picking a fixed pivot. A last-element (or first-element) pivot turns sorted input into the O(n2) worst case. Randomise or use median-of-three.
- Off-by-one in partition bounds. The recursive calls must exclude the pivot:
low..p-1andp+1..high. Including the pivot can loop forever. - Forgetting the base case. The
if (low < high)guard stops the recursion on segments of size zero or one. Omitting it overflows the stack. - Expecting stability. The swaps in partitioning can reorder equal elements. If you need stable sorting, use merge sort.
- Worrying only about average. For adversarial or already-sorted data, the naive version is genuinely O(n2). Always pivot defensively in production code.
FAQ
Why is quick sort often faster than merge sort despite the worse worst case? It sorts in place with excellent cache locality and low constant overhead, so on typical data with a good pivot it usually beats merge sort in real running time.
Is quick sort stable? Not in its standard form — partitioning can swap equal elements out of order. Use merge sort when stability is required.
Keep learning
Compare it directly with Merge Sort, revisit the recursion patterns it depends on, and browse the full DSA hub.
Want to implement quick sort and pivot strategies with instructor feedback in Jalgaon? Join the waitlist for our Data Structures & Algorithms course.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFounder, Infoplanet
Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.
Related guides
Arrays as a Data Structure
A clear introduction to arrays: how contiguous storage gives O(1) random access, why inserting in the middle is O(n), and when to reach for an array versus another structure.
Big-O Notation & Time Complexity, Simply Explained
A plain-language guide to Big-O notation and time complexity, covering the common growth rates with worked examples so you can reason about how fast your code really is.
Binary Search Trees Explained
Understand the binary search tree: how the left-smaller, right-larger rule enables O(log n) search on a balanced tree, and why balance is the whole game.
