The Complete 2026 Reference

Sorting Algorithms —
Every Answer, Visualised

The only sorting algorithm guide that answers every real question: which is fastest, which to pick for interviews, how they compare, and why theory sometimes lies. With live interactive examples.

🏆 Fastest Algorithm 📋 Big-O Cheatsheet ⚔ QuickSort vs MergeSort ⚖ Stability Explained 📖 All 10 Algorithms 🎯 Which to Use When 🎤 Interview Guide 🌍 Real-World Usage

What is the fastest sorting algorithm?

⚡ Sort & Visualize Verdict
It depends — but QuickSort wins in most real scenarios.

QuickSort averages O(n log n) with exceptional cache efficiency, making it 2–3× faster than MergeSort on in-memory arrays. For integers in a bounded range, Counting Sort or Radix Sort run in O(n) — theoretically faster than any comparison sort. For nearly-sorted real-world data, TimSort wins. There is no single "fastest" — there is only the fastest for your data.

⏱ SPEED RACE — 100,000 random integers, in-memory (relative performance)
QuickSort
FASTEST
O(n log n)
IntroSort (C++)
≈QS
O(n log n)
TimSort
fast
O(n log n)
MergeSort
good
O(n log n)
HeapSort
ok
O(n log n)
ShellSort
ok
O(n log²n)
InsertionSort
slow
O(n²)
BubbleSort
O(n²)
💡
Why QuickSort beats MergeSort in practice despite equal Big-O: QuickSort sorts in-place, so it makes sequential memory accesses that fit beautifully into CPU cache lines. MergeSort copies data to auxiliary arrays — those non-local memory reads are expensive. On a modern CPU with L1/L2 cache, this cache locality advantage makes QuickSort 2–3× faster for typical in-memory workloads.
⚠️
QuickSort's Achilles' heel: On already-sorted or reverse-sorted input with a naive pivot (first/last element), QuickSort degrades to O(n²). Always use a randomised pivot or median-of-three strategy in production code.

Sorting Algorithm Complexity Cheatsheet

Every algorithm's time and space complexity at a glance. Bookmark this. It's what you'll reach for before every interview and every code review.

Algorithm Best Case Average Case Worst Case Space Stable? Method
Bubble SortO(n)O(n²)O(n²)O(1)✔ StableExchange
Selection SortO(n²)O(n²)O(n²)O(1)✗ UnstableSelection
Insertion SortO(n)O(n²)O(n²)O(1)✔ StableInsertion
Merge SortO(n log n)O(n log n)O(n log n)O(n)✔ StableMerge D&C
Quick SortO(n log n)O(n log n)O(n²)*O(log n)✗ UnstablePartition D&C
Heap SortO(n log n)O(n log n)O(n log n)O(1)✗ UnstableSelection
Shell SortO(n log n)O(n log²n)O(n log²n)O(1)✗ UnstableInsertion
Counting SortO(n+k)O(n+k)O(n+k)O(k)✔ StableNon-comparison
Radix SortO(nk)O(nk)O(nk)O(n+k)✔ StableNon-comparison
Tim SortO(n)O(n log n)O(n log n)O(n)✔ StableHybrid

* QuickSort worst-case O(n²) is rare in practice and avoided with randomised pivot. k = range of input values. n = number of elements.

🎯
Interview tip: Examiners love to ask about worst-case. Memorise that QuickSort's worst case is O(n²) (though avoidable), and that MergeSort, HeapSort, and TimSort all guarantee O(n log n) in all cases. Counting and Radix Sort beat this lower bound by not using comparisons — but only for specific input types.

QuickSort vs MergeSort — The Definitive Verdict

This is the single most-asked sorting algorithm interview question. Here is the honest, nuanced answer — not the generic textbook version.

QuickSort ↑ Wins for arrays
Average speed2–3× faster*
SpaceO(log n) in-place
Cache localityExcellent
Stable?No
Worst caseO(n²) naïve
External sortNo
Linked listsPoor
Used inC++ std::sort*
VS
MergeSort ↑ Wins for guarantees
Average speedConsistently good
SpaceO(n) auxiliary
Cache localityPoor (non-local)
Stable?Yes
Worst caseO(n log n)
External sortYes (disk sorting)
Linked listsExcellent
Used inTimSort (Python, Java)

The Deeper Story: Cache Lines and Why Theory Lies

Both QuickSort and MergeSort have O(n log n) average complexity — identical on paper. But constants matter, and those constants are dominated by how your CPU handles memory. QuickSort pivots around a single element and swaps nearby elements, so all accesses stay in L1/L2 cache (64–256KB). MergeSort allocates a fresh O(n) array at every merge step, causing cache misses. On a modern CPU sorting 1M integers, QuickSort finishes in ~80ms; MergeSort takes ~200ms — same Big-O, 2.5× real difference.

C++'s std::sort uses IntroSort — a hybrid that starts as QuickSort, switches to HeapSort if recursion depth exceeds 2·log(n) (to avoid O(n²) worst case), and falls back to InsertionSort for small arrays. Java's Arrays.sort() for primitives uses Dual-Pivot QuickSort (Yaroslavskiy, 2009); for objects it uses TimSort to preserve stability.

📌
Definitive answer for interviews: "QuickSort is faster in practice for in-memory arrays due to cache locality. MergeSort is preferred when you need guaranteed O(n log n), stable sort, or when sorting linked lists or external data. The real world uses hybrids — IntroSort in C++, TimSort in Python and Java."

What does stable mean — and why does it matter?

A sorting algorithm is stable if it preserves the original relative order of elements that compare as equal. This sounds abstract — here's why it's crucial in practice.

A Concrete Example: Sorting a Student Roster

Imagine you have a list of students, each with a name and grade. You want to sort first by grade, then by name within each grade.

Input (sorted by name already)
Alice — A
Bob — B
Carol — A
Dave — B
Eve — A
Sort by grade with Stable sort
Alice — A
Carol — A
Eve — A
Bob — B
Dave — B

With a stable sort, Alice, Carol, and Eve maintain their alphabetical order within grade A — the sub-sort is preserved for free. An unstable sort may output Eve, Alice, Carol in any order.

✔ Stable Algorithms
Bubble Sort
Insertion Sort
Merge Sort
Tim Sort
Counting Sort
Radix Sort
✗ Unstable Algorithms
Quick Sort
Heap Sort
Selection Sort
Shell Sort
IntroSort (C++)

Stability matters when you sort by multiple criteria sequentially, sort objects where equality is partial, or guarantee deterministic output for user-facing features (like sort-by-date that preserves name order). Python's built-in sorted() is stable by design — it was a deliberate choice that enables the Schwartzian transform idiom.

All 10 Algorithms — Fully Explained

Click any algorithm to expand the full technical breakdown: how it works, pseudocode, when to use it, and what to avoid.

Bubble Sort
O(n²) avg O(1) space Stable
How It Works

Repeatedly steps through the array, compares adjacent pairs, and swaps them if out of order. After each full pass, the largest unsorted element "bubbles" to its correct position at the end. The optimised version stops early if no swaps occurred in a pass — achieving O(n) on already-sorted input.

Best For
  • Learning and teaching algorithm fundamentals
  • Detecting if an array is already sorted (O(n) best case)
  • Arrays so small that code simplicity > performance
Avoid When
  • Any production sorting of non-trivial data sizes
  • N > ~100 elements
Pseudocode
procedure bubbleSort(A, n): repeat swapped ← false for i ← 0 to n−2 do if A[i] > A[i+1] then swap(A[i], A[i+1]) swapped ← true n ← n − 1 until not swapped // With swapped flag: O(n) best case
▶ Watch Bubble Sort animate live →
Insertion Sort
O(n) best O(n²) avg Stable
How It Works

Maintains a sorted sub-array from the left. For each new element, "inserts" it into the correct position by shifting larger sorted elements right. Like sorting playing cards in your hand — you pick each card and slide it into position.

Key insight: InsertionSort is the algorithm of choice for small arrays (n ≤ 32). Both TimSort and IntroSort use InsertionSort internally for subarrays below a threshold because its low overhead beats O(n log n) algorithms at small scales.

Best For
  • Nearly-sorted data (O(n·k) where k = max displacement)
  • Online sorting — elements arriving one at a time
  • Small arrays or as subroutine in hybrid sorts
Pseudocode
procedure insertionSort(A): for i ← 1 to length(A)−1 do key ← A[i] j ← i − 1 while j ≥ 0 and A[j] > key do A[j+1] ← A[j] // shift right j ← j − 1 A[j+1] ← key // insert // O(n) on nearly sorted — uses no swaps, // only shifts (cache friendly)
▶ Watch Insertion Sort animate live →
Selection Sort
O(n²) always O(1) space Unstable
How It Works

Divides the array into sorted and unsorted portions. On each pass, scans the unsorted portion to find the minimum element, then swaps it into the correct position. Exactly n−1 swaps total, regardless of input.

Why it matters despite O(n²): In environments where writes are very expensive (e.g. EEPROM, flash memory with limited write cycles), Selection Sort's guaranteed O(n) swaps makes it the right choice even though comparisons are still O(n²).

Best For
  • Minimising write operations (flash memory, EEPROM)
  • When write cost >> read cost
  • Teaching selection-based algorithm design
Pseudocode
procedure selectionSort(A): n ← length(A) for i ← 0 to n−1 do minIdx ← i for j ← i+1 to n−1 do if A[j] < A[minIdx] then minIdx ← j swap(A[i], A[minIdx]) // Always O(n²) — no early exit possible // Exactly n−1 swaps regardless of input
▶ Watch Selection Sort animate live →
Merge Sort
O(n log n) always O(n) space Stable
How It Works

Classic divide-and-conquer: recursively split the array in half until each subarray has one element (trivially sorted), then merge pairs of sorted subarrays into a single sorted array. Invented by John von Neumann in 1945.

External sorting: MergeSort is the algorithm of choice for sorting data that doesn't fit in memory (database records, log files). You can merge sorted chunks from disk without loading everything into RAM.

Best For
  • When stability is non-negotiable
  • External sorting (data larger than RAM)
  • Sorting linked lists (no random access needed)
  • Guaranteed O(n log n) — no worst-case surprise
Pseudocode
procedure mergeSort(A, l, r): if l ≥ r then return mid ← (l + r) / 2 mergeSort(A, l, mid) mergeSort(A, mid+1, r) merge(A, l, mid, r) procedure merge(A, l, mid, r): left ← A[l..mid] right ← A[mid+1..r] i,j,k ← 0,0,l while i < |left| and j < |right|: if left[i] ≤ right[j]: A[k++] ← left[i++] else: A[k++] ← right[j++]
▶ Watch Merge Sort animate live →
Quick Sort ⚡
O(n log n) avg O(n²) worst Unstable
How It Works

Selects a pivot element and partitions the array so all smaller elements move left and all larger elements move right. Recursively applies to both partitions. Invented by Tony Hoare in 1959–1961. The fastest in-memory comparison sort for random data.

Pivot strategies: (1) Last element — simple but O(n²) on sorted input. (2) Random pivot — avoids adversarial inputs. (3) Median-of-three — best practical heuristic. (4) Dual-pivot — Java's approach, reduces average comparisons by ~5%.

Best For
  • General-purpose in-memory sorting
  • Arrays that fit in CPU cache
  • When average-case speed matters most
  • Random or uniformly distributed data
Pseudocode (Randomised)
procedure quickSort(A, lo, hi): if lo < hi then pivot ← randomPivot(lo, hi) swap(A[pivot], A[hi]) p ← partition(A, lo, hi) quickSort(A, lo, p−1) quickSort(A, p+1, hi) procedure partition(A, lo, hi): pivot ← A[hi]; i ← lo−1 for j ← lo to hi−1 do if A[j] ≤ pivot: i++; swap(A[i], A[j]) swap(A[i+1], A[hi]) return i+1
▶ Watch Quick Sort animate live →
Heap Sort
O(n log n) always O(1) space Unstable
How It Works

Uses a max-heap data structure. Phase 1: Build a max-heap in O(n) time — rearrange the array so every parent is larger than its children. Phase 2: Repeatedly extract the maximum (root), swap it to the end, then re-heapify the reduced heap. Gives O(n log n) guaranteed with O(1) extra space.

The catch: Poor cache locality (accesses jump around the array non-sequentially) makes it slower than QuickSort by a constant factor, despite identical Big-O. Used mainly when O(1) space and O(n log n) worst-case are both required.

Best For
  • Memory-constrained environments (O(1) space)
  • Real-time systems needing worst-case guarantees
  • Part of IntroSort (fallback when QS recurses too deep)
Pseudocode
procedure heapSort(A): buildMaxHeap(A) // O(n) for i ← n−1 downto 1 do swap(A[0], A[i]) // max → end heapify(A, 0, i) // O(log n) procedure heapify(A, i, n): largest ← i l,r ← 2i+1, 2i+2 if l < n and A[l] > A[largest]: largest ← l if r < n and A[r] > A[largest]: largest ← r if largest ≠ i: swap(A[i], A[largest]) heapify(A, largest, n)
▶ Watch Heap Sort animate live →
Shell Sort
O(n log²n) avg O(1) space Unstable

A generalisation of Insertion Sort. Instead of comparing adjacent elements, Shell Sort compares elements separated by a "gap" that shrinks each pass. Large gaps allow elements to move long distances quickly; by the final pass (gap=1), the array is nearly sorted so InsertionSort completes in near-linear time. Performance depends heavily on gap sequence choice — Ciura's gap sequence (1, 4, 10, 23, 57, 132, 301, 701…) gives the best known results empirically.

  • Embedded systems or environments where recursion is prohibited
  • Medium datasets where O(n log n) sorts aren't available
  • When O(1) space and reasonable speed are both needed
▶ Watch Shell Sort →
Counting Sort
O(n+k) — linear! O(k) space Stable

Breaks the O(n log n) comparison-sort lower bound by not comparing elements at all. Counts occurrences of each value in a frequency table of size k (range of input), then reconstructs the sorted array by iterating the count table. O(n+k) time and space, where k = max value − min value.

The critical constraint: Only works for non-negative integers (or values mappable to integers) in a known, bounded range. If k >> n (e.g. sorting 100 values in range 0–1,000,000), the O(k) space and time overhead makes it impractical. It shines when k is small relative to n — sorting grades (0–100), ages (0–120), ASCII characters (0–127), etc.

  • Grades, ages, character frequencies, small-range integers
  • As subroutine in Radix Sort
  • When n and k are both reasonable in size
▶ Watch Counting Sort →
Radix Sort
O(nk) — near-linear O(n+k) space Stable

Sorts integers digit-by-digit, from least significant digit (LSD) to most significant (MSD), using Counting Sort as a stable subroutine at each digit position. After d passes (where d = number of digits), the array is fully sorted. O(d·n) total — effectively O(n) for fixed-width integers.

Real-world applications: Sorting IP addresses, phone numbers, fixed-length codes (ZIP codes, credit card numbers), and is used in database index sorting. In GPU-accelerated computing, Radix Sort outperforms QuickSort because its memory access pattern is more parallelisable.

▶ Watch Radix Sort →
Tim Sort 🌟
O(n) best O(n log n) worst Stable
How It Works

Designed by Tim Peters for Python 2.3 (2002), TimSort is the most practically important sorting algorithm today — it's the default in Python (list.sort(), sorted()), Java (Arrays.sort(Object[])), Kotlin, Swift, and Android.

It detects naturally occurring runs (already-sorted sequences) in the data. Short runs are extended to minimum size (RUN ≈ 32–64) using InsertionSort. These runs are then merged using a MergeSort variant with sophisticated merge-order optimisation (the galloping mode). On real-world data that contains partial ordering, TimSort can approach O(n) speed.

Why It's The Real-World Champion
  • Real-world data is rarely fully random — partial order is common
  • Database records, log files, user events often arrive partially sorted
  • TimSort exploits this, MergeSort and QuickSort ignore it
  • Stable, correct, and fast in all cases — the complete package
Simplified Pseudocode
procedure timSort(A): RUN ← 32 n ← length(A) // Phase 1: sort all runs for i ← 0 to n step RUN: insertionSort(A, i, min(i+RUN−1, n−1)) // Phase 2: merge runs size ← RUN while size < n: for left ← 0 to n step 2×size: mid ← left + size − 1 right ← left + 2×size − 1 if mid < right: merge(A, left, mid, right) size ← size × 2
Used in: Python, Java (objects), Kotlin, Swift, Android, V8 (JavaScript)
▶ Watch Tim Sort animate live →

Which sorting algorithm should you use?

Stop memorising everything — learn the decision tree. Every real-world choice comes down to these questions.

🎯 Algorithm Selector — answer these questions in order
Is your data already mostly sorted (≥80% in order)?
Insertion Sort or TimSort
Are you sorting small n (≤20 elements)?
Insertion Sort
Is your data bounded integers with a known, small range?
Counting Sort
Sorting fixed-width integers or strings (e.g. IDs, phone numbers)?
Radix Sort
Stability required (preserve order of equal elements)?
Merge Sort or TimSort
Sorting a linked list (no random access)?
Merge Sort
Memory is critical (O(1) space required, large n)?
Heap Sort
External sort (data larger than RAM, on disk)?
Merge Sort (external)
General purpose — random data, in memory, just needs fast?
QuickSort (randomised)
Using a standard library? (Python, Java, C++, JavaScript)
Just use the built-in sort ✓
🔑
The honest truth: In 99% of real production code, the right answer is "use the language's built-in sort". Python's sorted(), Java's Arrays.sort(), JavaScript's Array.prototype.sort() — these are all tuned, production-hardened hybrids. You need to choose yourself only when (a) you have very specific data characteristics, (b) you have unique constraints (memory, stability, data type), or (c) you're answering an interview question.

The 8 sorting algorithm interview questions you will face

These are the questions hiring managers at FAANG-tier companies actually ask. Not exhaustive — targeted. Know these cold.

Q 01
Why is QuickSort preferred over MergeSort even though their average time complexity is the same?
Answer: Cache locality. QuickSort is in-place; its memory accesses stay in L1/L2 cache. MergeSort allocates O(n) auxiliary memory and its merge step causes cache misses. On modern hardware, QuickSort is 2–3× faster in practice despite identical Big-O.
Q 02
What sorting algorithm does Python use and why?
Answer: TimSort — a hybrid of MergeSort and InsertionSort. Chosen because it's stable, achieves O(n) on nearly-sorted data (common in real-world datasets), and provides O(n log n) worst-case. Designed by Tim Peters specifically for Python in 2002.
Q 03
Can you sort in better than O(n log n)? What's the lower bound for comparison sorts?
Answer: The information-theoretic lower bound for comparison-based sorts is Ω(n log n) — any algorithm that uses only comparisons requires at least n log n operations. But non-comparison sorts (Counting Sort, Radix Sort) sidestep this by using integer structure, achieving O(n+k) or O(nk) time.
Q 04
What is an in-place sorting algorithm? Which ones are in-place?
Answer: In-place means the algorithm sorts using O(1) extra memory (or O(log n) for stack space in recursive algorithms). In-place: QuickSort (O(log n) stack), HeapSort (O(1)), InsertionSort, BubbleSort, SelectionSort, ShellSort. Not in-place: MergeSort O(n), TimSort O(n), CountingSort O(k), RadixSort O(n+k).
Q 05
When would you use HeapSort over QuickSort?
Answer: When you need guaranteed O(n log n) worst-case and O(1) space simultaneously. HeapSort is the only common algorithm satisfying both. QuickSort has O(n²) worst case; MergeSort uses O(n) space. HeapSort is also the fallback in C++'s IntroSort when recursion depth exceeds 2·log(n).
Q 06
You need to sort a list of objects by last name, then by first name. How?
Answer: Use a stable sort. First sort by first name (stable), then sort by last name (stable). Because the sort is stable, equal last names preserve their first-name ordering from step 1. In Python: sorted(people, key=lambda x: (x.last, x.first)) — single-pass with tuple key.
Q 07
How would you sort 1 million integers where each value is between 0 and 1000?
Answer: Counting Sort. Range k = 1001, n = 1,000,000. Time O(n+k) ≈ O(n). This dramatically outperforms QuickSort's O(n log n) ≈ 20n here. Space: O(k) = O(1001) ≈ O(1) effectively. The bounded integer range is the signal — always think Counting/Radix Sort when you see it.
Q 08
What is the worst case input for QuickSort and how do you avoid it?
Answer: Already sorted (or reverse-sorted) array with naive first/last-element pivot — produces O(n²) because every partition is maximally unbalanced. Solutions: (1) Random pivot — makes O(n²) astronomically unlikely. (2) Median-of-three — picks median of first, middle, last. (3) IntroSort — switches to HeapSort after 2·log(n) recursive depth.

Sorting algorithms in the real world

Theory is one thing. Here's what the systems you use every day actually run under the hood.

🐍
Python
TimSort
Stable, fast on real-world partially-ordered data. Both list.sort() and sorted() use it. Designed by Tim Peters, CPython dev, in 2002.
Java / Android
TimSort + Dual-Pivot QuickSort
Objects use TimSort (stability required). Primitives use Dual-Pivot QuickSort (Yaroslavskiy, 2009) — fewer cache misses than single-pivot QS.
C++ (STL)
IntroSort
std::sort uses IntroSort: starts as QuickSort, falls to HeapSort if depth > 2·log(n), uses InsertionSort for small partitions. Best of all worlds.
🦅
Swift
TimSort
Swift's standard library adopted TimSort for its stability guarantees and excellent performance on partially-sorted collections common in UI frameworks.
🗄️
PostgreSQL
External Merge Sort
Database ORDER BY uses MergeSort variants because data doesn't fit in memory — sorted runs are written to disk and merged. Merge Sort's sequential access pattern suits disk I/O perfectly.
🔍
Google Search
Multiple algorithms
Different stages use different algorithms: indexing uses external merge sort; ranking uses heap-based top-k selection; result merging uses merge-based combination of sorted posting lists.
🎮
GPU / CUDA
Radix Sort
GPU Radix Sort vastly outperforms QuickSort on GPUs because its fixed, predictable memory access pattern allows massive parallelism. Used in NVIDIA's thrust library.
🌐
V8 (JavaScript)
TimSort (since V8 7.0)
JavaScript's Array.prototype.sort() was unstable until V8 7.0 (Chrome 70). Now uses TimSort — the spec mandates stability since ECMAScript 2019.
🌟
The pattern: TimSort has conquered almost every modern general-purpose language runtime because it handles the real world — where data is partially ordered, stability matters, and pathological cases must be avoided. QuickSort dominates for raw number-crunching in C/C++ and GPU-accelerated workloads. Radix Sort rules when data is integers and you control the pipeline.
Theory is nothing without seeing it.

Every algorithm on this page is interactive in the visualiser — watch it run, pause it, step through it frame by frame.

Launch Sort & Visualize Visualiser