๐ŸงฉIntro to Algorithms

Sorting Algorithm Time Complexities

Study smarter with Fiveable

Get study guides, practice questions, and cheatsheets for all your subjects. Join 500,000+ students with a 96% pass rate.

Get Started

Why This Matters

Sorting algorithms are the backbone of efficient data processing, and understanding their time complexities is fundamental to algorithm analysis. Exams expect you to do more than recite Big-O notation. You need to analyze trade-offs between algorithms, predict performance based on input characteristics, and justify algorithm selection for specific scenarios. These concepts connect directly to divide-and-conquer strategies, space-time trade-offs, comparison vs. non-comparison models, and asymptotic analysis.

When you encounter a sorting question, the goal is to show you understand why an algorithm performs the way it does, not just what its complexity is. Can you explain why Quick Sort degrades to O(n2)O(n^2)? Do you know when Counting Sort beats Merge Sort? Don't just memorize the table at the bottom of this guide. Know what mechanism each algorithm uses and when that mechanism fails or excels.


Quadratic Algorithms: Simple but Slow

These O(n2)O(n^2) algorithms use nested iterations to compare and swap elements. Their simplicity makes them easy to implement, but quadratic growth means they become impractical as input size increases. Know these for their educational value and niche use cases.

Bubble Sort

  • Time complexity: O(n2)O(n^2) in the average and worst cases. It compares adjacent elements repeatedly, "bubbling" larger values toward the end of the array.
  • Adaptive optimization possible. If no swaps occur during a full pass, the algorithm can terminate early, giving a O(n)O(n) best case on already-sorted input.
  • Primary use case: educational. Demonstrates sorting fundamentals but is rarely used in production code.

Selection Sort

  • Time complexity: O(n2)O(n^2) in all cases. It always scans the entire unsorted region to find the minimum, regardless of input order. There is no early termination.
  • Minimizes swaps. It performs exactly nโˆ’1n - 1 swaps total, which can matter when write operations are expensive (e.g., writing to flash memory).
  • In-place but not stable. Requires O(1)O(1) extra space, but swapping distant elements can reorder equal values.

Insertion Sort

  • Time complexity: O(n2)O(n^2) worst case, O(n)O(n) best case. It shifts elements rightward to insert each item into its correct position within the sorted portion.
  • Adaptive and stable. It naturally runs faster on nearly sorted data because fewer shifts are needed, and it preserves the relative order of equal elements.
  • Preferred for small datasets. Often used as the base case in hybrid algorithms like Timsort (Python's built-in sort), which switches to Insertion Sort for small subarrays.

Compare: Selection Sort vs. Insertion Sort. Both are O(n2)O(n^2), but Insertion Sort is adaptive (faster on partially sorted data) while Selection Sort always performs the same number of comparisons. If asked which quadratic sort to use on nearly sorted data, Insertion Sort is your answer.


Divide-and-Conquer: The O(nlogโกn)O(n \log n) Standard

These algorithms achieve better performance by recursively breaking the problem into smaller subproblems. The O(nlogโกn)O(n \log n) bound is the theoretical lower bound for comparison-based sorting, proven via a decision-tree argument. Any comparison-based sort must make at least O(nlogโกn)O(n \log n) comparisons in the worst case.

Merge Sort

  • Time complexity: O(nlogโกn)O(n \log n) guaranteed. It divides the array in half, recursively sorts each half, then merges the two sorted halves in linear time. The recurrence is T(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n).
  • Space complexity: O(n)O(n). Requires auxiliary arrays for the merge step, making it not in-place.
  • Stable sort. Preserves relative order of equal elements, which is critical for multi-key sorting scenarios (e.g., sort by last name, then by first name).

Quick Sort

  • Average case: O(nlogโกn)O(n \log n), worst case: O(n2)O(n^2). Performance depends entirely on pivot selection.
  • In-place partitioning. Requires only O(logโกn)O(\log n) stack space for recursion (average case), making it more memory-efficient than Merge Sort.
  • Worst case occurs with poor pivots. If the pivot consistently lands at the extreme end of the array (e.g., choosing the first element on already-sorted input), one partition has nโˆ’1n - 1 elements and the other has 0. This produces nn levels of recursion instead of logโกn\log n, giving O(n2)O(n^2). Randomized pivot selection or median-of-three strategies mitigate this.

Heap Sort

  • Time complexity: O(nlogโกn)O(n \log n) guaranteed. Builds a max-heap in O(n)O(n) time, then repeatedly extracts the maximum element (each extraction costs O(logโกn)O(\log n)).
  • In-place but not stable. Uses O(1)O(1) extra space, but heap operations can reorder equal elements.
  • No worst-case degradation. Unlike Quick Sort, it performs consistently regardless of input distribution. However, its poor cache locality (it jumps around in memory during heap operations) often makes it slower in practice than Quick Sort.

Compare: Merge Sort vs. Quick Sort. Both average O(nlogโกn)O(n \log n), but Merge Sort guarantees this while Quick Sort can degrade to O(n2)O(n^2). However, Quick Sort is in-place while Merge Sort requires O(n)O(n) extra space. For a question about space-constrained sorting with good average performance, Quick Sort is your answer.

Compare: Quick Sort vs. Heap Sort. Both are in-place, but Heap Sort guarantees O(nlogโกn)O(n \log n) while Quick Sort risks O(n2)O(n^2). In practice, Quick Sort's better cache efficiency often makes it faster despite the theoretical risk.


Non-Comparison Sorts: Breaking the O(nlogโกn)O(n \log n) Barrier

These algorithms bypass the comparison model entirely, achieving linear time under specific conditions. They exploit structural properties of the input data (like integer range or digit count) rather than comparing elements directly.

Counting Sort

  • Time complexity: O(n+k)O(n + k), where kk is the range of input values. It counts how many times each value appears, then reconstructs the sorted output from those counts.
  • Space complexity: O(n+k)O(n + k). Requires a count array of size kk plus an output array of size nn. This becomes impractical when kk is very large (e.g., sorting arbitrary 64-bit integers where kk could be enormous).
  • Stable and non-comparison. Its stability guarantee is what makes it useful as a subroutine inside Radix Sort.

Radix Sort

  • Time complexity: O(d(n+k))O(d(n + k)), where dd is the number of digits and kk is the base (radix). For example, sorting nn numbers that each have at most 6 decimal digits gives d=6d = 6 and k=10k = 10.
  • Processes digits sequentially. It applies a stable sort (typically Counting Sort) at each digit position, working from least significant to most significant digit. Stability is essential here: it ensures that the ordering from previous digit passes is preserved.
  • Efficient for fixed-length integers or strings. Outperforms comparison sorts when dd is small relative to logโกn\log n. If dd grows proportionally to logโกn\log n, Radix Sort's advantage disappears.

Bucket Sort

  • Time complexity: O(n+k)O(n + k) average, where kk is the number of buckets. It distributes elements into kk buckets, sorts each bucket individually, then concatenates the results.
  • Assumes uniform distribution. If elements cluster in a few buckets, those buckets become large and the per-bucket sort dominates, potentially reaching O(n2)O(n^2) overall.
  • Hybrid approach. Typically uses Insertion Sort for individual buckets, since each bucket is expected to be small when the distribution is uniform.

Compare: Counting Sort vs. Radix Sort. Counting Sort handles single-key integers with a limited range. Radix Sort extends this to multi-digit numbers by applying Counting Sort digit-by-digit. When the range kk is too large for a single pass of Counting Sort, Radix Sort becomes the alternative.

Compare: Comparison vs. Non-Comparison sorts. O(nlogโกn)O(n \log n) is the proven lower bound for comparison-based sorting, but non-comparison sorts like Counting and Radix can achieve O(n)O(n) by exploiting input structure. If a question asks how to sort faster than O(nlogโกn)O(n \log n), these are your go-to examples, and you should specify the constraints that make them applicable.


Quick Reference Table

ConceptBest Examples
Guaranteed O(nlogโกn)O(n \log n)Merge Sort, Heap Sort
In-place sortingQuick Sort, Heap Sort, Selection Sort, Insertion Sort
Stable sortingMerge Sort, Insertion Sort, Counting Sort, Radix Sort
Adaptive (benefits from sorted input)Insertion Sort, Bubble Sort
Linear time possibleCounting Sort, Radix Sort, Bucket Sort
Divide-and-conquerMerge Sort, Quick Sort
Non-comparison basedCounting Sort, Radix Sort, Bucket Sort
Best for small/nearly sorted dataInsertion Sort

Self-Check Questions

  1. Which two O(nlogโกn)O(n \log n) algorithms guarantee their time complexity regardless of input, and what trade-off does each make to achieve this?

  2. You need to sort 10 million 32-bit integers with limited extra memory. Compare Quick Sort and Merge Sort for this scenario. Which would you choose and why?

  3. Explain why Radix Sort can achieve better than O(nlogโกn)O(n \log n) performance. Under what conditions would it actually be slower than Quick Sort?

  4. A dataset is known to be "almost sorted" with only a few elements out of place. Which algorithm would you recommend from each complexity class (quadratic and O(nlogโกn)O(n \log n)), and why?

  5. Compare the space requirements of Merge Sort, Quick Sort, and Counting Sort. For each, identify a scenario where its space usage would be problematic.