DAY 11 / 30 · WEEK 2

Quick Sort

Goal of the day

Concept

Line up a group of people to sort by height. Pick one person as a reference point — the pivot — and have everyone shorter step to their left, everyone taller step to their right. Once that's done, the pivot is standing in exactly the spot they'll occupy in the final sorted line — nobody will ever move them again. Now repeat the same process independently on the "shorter" group and the "taller" group, each shrinking the problem until every group has 0 or 1 people left.

This is different from Day 10's merge sort: merge sort does easy splits (always exactly in half) but hard combines (a real merge step). Quicksort does hard splits (the partition does real work) but trivial combines (there's nothing left to do — once both sides are sorted, the whole array is sorted, because the pivot was already in place).

Diagram

Partition around pivot = 3 (last element) 5 2 8 1 3 2 1 3 5 8 smaller ≤ pivot not yet partitioned 3 is now at index 2 — its FINAL position. Everything left of it is ≤ 3, everything right is not yet sorted but will never contain a value that belongs to the left of index 2. Recurse on each side.

Interactive visualization: full sort, partition + recurse

pivot comparing to pivot swapping in final position
Press Play or Step to begin.

The code

function quickSort(arr, lo = 0, hi = arr.length - 1) {
  if (lo < hi) {
    const p = partition(arr, lo, hi);
    quickSort(arr, lo, p - 1);   // everything left of the pivot
    quickSort(arr, p + 1, hi);   // everything right of the pivot
  }
  return arr;
}

function partition(arr, lo, hi) {
  const pivot = arr[hi];   // pick the last element as pivot
  let i = lo - 1;          // boundary of "elements ≤ pivot seen so far"
  for (let j = lo; j < hi; j++) {
    if (arr[j] <= pivot) {
      i++;
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
  }
  [arr[i + 1], arr[hi]] = [arr[hi], arr[i + 1]]; // pivot into its final slot
  return i + 1; // the pivot's final index
}

Line by line:

Dry run: partition([5, 2, 8, 1, 3], lo=0, hi=4, pivot=3)

jarr[j]≤ pivot?ActionArray after
05No[5, 2, 8, 1, 3]
12Yesi=0, swap(0,1)[2, 5, 8, 1, 3]
28No[2, 5, 8, 1, 3]
31Yesi=1, swap(1,3)[2, 1, 8, 5, 3]
final swap(2, 4)[2, 1, 3, 5, 8]

3 lands at index 2 — everything before it (2, 1) is ≤ 3, everything after (5, 8) is not yet sorted but is guaranteed > 3.

Complexity

CaseTimeSpaceWhy
AverageO(n log n)O(log n)Pivots tend to roughly halve the array — log n levels of recursion, O(n) work per level.
WorstO(n²)O(n)A consistently bad pivot (e.g. always the smallest/largest) splits n elements into 1 and n−1 — n levels deep instead of log n.

Space is the recursion depth, not extra arrays — quicksort partitions in place, unlike merge sort's O(n) auxiliary arrays. That's quicksort's main practical advantage.

Interview corner

How to talk through it out loud: name the trade-off explicitly — "quicksort partitions in place, so it avoids merge sort's O(n) extra space, but that trade-off is a worst case that depends on pivot choice." Following up with how you'd choose a pivot to avoid the worst case (random or median-of-three) shows you know the failure mode and how to defend against it, not just the happy path.

Practice problems

1. Partition Array According to Given Pivot (Easy — LeetCode: Partition Array According to Given Pivot)

Given an array nums and a value pivot, rearrange the elements so everything less than pivot comes first, then everything equal to it, then everything greater — but unlike today's in-place partition, you must preserve each group's original relative order.

2. Kth Largest Element in an Array (Medium — LeetCode: Kth Largest Element in an Array)

Find the kth largest element in an unsorted array. (You solved this with a selection-sort approach on Day 8b — today, solve it faster.)

3. 3Sum (Medium — LeetCode: 3Sum)

Given an array of integers, find all unique triplets that sum to zero. (Combines Day 4's two-pointer technique with sorting — a classic pairing of today's and an earlier day's ideas.)

Quiz

1. What causes quicksort's O(n²) worst case?

2. After one call to partition(), what's guaranteed about the pivot's position?

3. What's quicksort's main practical advantage over merge sort?

4. Is the standard (Lomuto partition) quicksort stable?

5. What's the standard defense against quicksort's worst case?