Sorting Algorithms

Sorting is one of the most fundamental operations in programming. At its core, it means arranging elements into a defined order — typically ascending or descending. While modern languages provide excellent built-in sort functions (Python's sorted(), JavaScript's Array.sort()), understanding the underlying algorithms matters for two key reasons:

  1. You can reason about performance. Knowing whether your sort is O(n log n) or O(n²) helps you predict how code will behave when data grows from hundreds to millions of records.
  2. You can make informed choices. Different algorithms have different trade-offs in memory usage, stability, and performance on specific input shapes (like nearly sorted data). The right choice depends on your situation.

The algorithms covered here form the conceptual foundation for real-world sorts. Python's Timsort, for example, is a hybrid of merge sort and insertion sort. Understanding the building blocks helps you evaluate code — including code generated by AI tools.

Overview#

AlgorithmBestAverageWorstSpaceStable
Bubble SortO(n)O(n²)O(n²)O(1)Yes
Selection SortO(n²)O(n²)O(n²)O(1)No
Insertion SortO(n)O(n²)O(n²)O(1)Yes
Merge SortO(n log n)O(n log n)O(n log n)O(n)Yes
Quick SortO(n log n)O(n log n)O(n²)O(log n)No
Heap SortO(n log n)O(n log n)O(n log n)O(1)No
Rendering diagram...

Bubble Sort#

Bubble sort is the most intuitive sorting algorithm to understand, which is why it is almost always taught first. The idea is simple: scan through the array repeatedly, and whenever two adjacent elements are in the wrong order, swap them. After each full pass, the largest unsorted element has "bubbled up" to its correct position at the end.

The name comes from this visual: large values float to the top of the array like bubbles rising through water, one position at a time.

One important optimization makes bubble sort practical on already-sorted data: if a full pass completes without a single swap, the array is sorted and we can stop early. This is what gives it an O(n) best case — a single pass confirms the array is already in order.

Rendering diagram...

Bubble Sort

Repeatedly swap adjacent elements that are out of order

Time:O(n²)Space:O(1)Best:O(n)Worst:O(n²)

Selection Sort#

Selection sort takes a different approach: instead of swapping neighbors repeatedly, it finds the smallest remaining element in each pass and moves it directly to its correct position with a single swap.

This gives selection sort an interesting property — it always makes exactly n − 1 swaps regardless of the input. In contrast, bubble sort can make O(n²) swaps in the worst case. If you are working with data where writes are expensive (such as writing to flash memory or a slow storage medium), minimizing swaps matters, and selection sort has an advantage here.

The downside is that selection sort performs the same number of comparisons whether the data is random or nearly sorted — it cannot short-circuit. Its time complexity is always O(n²).

Example: Sorting [64, 25, 12, 22, 11]
Rendering diagram...

Selection Sort

Repeatedly find the minimum element and place it at the beginning

Time:O(n²)Space:O(1)Best:O(n²)Worst:O(n²)

Insertion Sort#

Insertion sort mirrors the way most people intuitively sort a hand of playing cards. You pick up one card at a time and slide it left into the correct position among the cards you are already holding. The left portion of your hand is always sorted; you are just extending it one element at a time.

The key insight is that elements only move as far as they need to. On nearly sorted data — where elements are close to their final positions — very little shifting is required, which is why insertion sort achieves O(n) in the best case.

This property makes insertion sort a practical choice in two scenarios:

  • Small arrays: The overhead of more complex algorithms outweighs their theoretical efficiency advantage.
  • Nearly sorted data: When only a few elements are out of place, insertion sort outperforms merge sort and quick sort in practice.

This is also why hybrid algorithms like Python's Timsort switch to insertion sort when a subarray is small enough.

Example: Sorting [5, 2, 4, 6, 1, 3]
Rendering diagram...

Insertion Sort

Build sorted array by inserting each element into its correct position

Time:O(n²)Space:O(1)Best:O(n)Worst:O(n²)

Merge Sort#

Merge sort is built on a key insight: merging two already-sorted arrays into one sorted array is easy and takes linear time. If you can merge efficiently, you can sort anything by repeatedly dividing the problem in half until you reach single-element arrays (which are trivially sorted), then merging your way back up.

This divide-and-conquer strategy guarantees O(n log n) performance in all cases — best, average, and worst. There are no pathological inputs that degrade its performance. The trade-off is that merge sort requires O(n) extra memory to hold the temporary arrays during the merge step.

Merge sort is also stable, making it a reliable choice when the relative order of equal elements must be preserved. It is the algorithm of choice for sorting linked lists, since it does not require random access to elements — a property that rules out quick sort and heap sort in those situations.

Example: Sorting [38, 27, 43, 3, 9, 82, 10]
Rendering diagram...

Merge Sort

Divide array in half, recursively sort, then merge sorted halves

Time:O(n log n)Space:O(n)Best:O(n log n)Worst:O(n log n)

Quick Sort#

Quick sort is also divide-and-conquer, but it works differently from merge sort. Instead of dividing the array at the midpoint and merging afterward, quick sort rearranges the array in-place around a chosen pivot element, so that everything smaller than the pivot ends up to its left and everything larger ends up to its right. The pivot is then in its final sorted position. Recursively applying this to both sides sorts the entire array.

The choice of pivot is critical. In the best and average case — when the pivot divides the array roughly in half each time — quick sort runs in O(n log n). In the worst case — when the pivot is always the smallest or largest element (e.g., always choosing the first element on an already-sorted array) — every partition is maximally unbalanced and performance degrades to O(n²).

Despite this worst-case risk, quick sort tends to outperform merge sort in practice on random data. The reasons are:

  • It sorts in place, requiring only O(log n) stack space for recursion.
  • It has excellent cache locality — it accesses memory sequentially within each partition, which is friendly to modern CPU caches.

Real-world implementations address the worst-case risk by using smarter pivot selection, such as choosing a random element or the median of three candidates.

Example: Sorting [3, 7, 8, 5, 2, 1, 9, 4]
Rendering diagram...

Quick Sort

Partition array around a pivot, then recursively sort partitions

Time:O(n log n)Space:O(log n)Best:O(n log n)Worst:O(n²)Avg:O(n log n)

Choosing a Sorting Algorithm#

In practice, you should almost always reach for your language's built-in sort — it is battle-tested, optimized, and appropriate for the vast majority of cases. Understanding the decision-making process matters when you need to go beyond the defaults: when sorting large datasets, optimizing for memory, or working with special data shapes.

Rendering diagram...
SituationRecommended AlgorithmReason
Small array (< 50 elements)Insertion SortLow overhead, simple
Nearly sorted dataInsertion SortO(n) best case
Need guaranteed O(n log n)Merge Sort / Heap SortNo worst case degradation
Need stable sortingMerge SortPreserves relative order
General purpose, in-placeQuick SortFast average case, cache-friendly
Limited memoryHeap SortO(1) extra space
Linked listMerge SortNo random access needed

Stability in Sorting#

A stable sort preserves the relative order of elements with equal keys. In other words, if two elements compare as equal, they appear in the output in the same order they appeared in the input.

This might sound like a minor detail, but it has real practical consequences. Stable sorting is the mechanism that makes multi-key sorting work correctly: if you sort a list of records by a secondary key first, then by a primary key using a stable sort, the secondary-key ordering is preserved within each group of primary-key equals. Without stability, you would need to sort by multiple keys simultaneously in a single comparator — which is more complex.

Rendering diagram...

Stability Matters When...

Examples where stable sorting is important

Stable sorting enables multi-key sorting

Reviewing AI-Generated Code#

AI tools are good at generating sorting code quickly, but they make systematic mistakes in this area. The most common errors involve incorrectly describing an algorithm's complexity, stability, or behavior on edge cases. Developing the habit of asking the right follow-up questions will help you catch these issues before they cause problems in production.

When AI generates sorting code, verify both correctness and complexity:

IssueWhat to CheckCommon AI Mistake
Algorithm choiceIs it appropriate for the use case?Using O(n²) sort for large data
Stability claimDoes it preserve relative order?Claiming quick sort is stable
Space complexityExtra memory usedClaiming merge sort is O(1) space
Worst casePathological inputsNot handling sorted input for quick sort
Comparison functionCustom sorting logicInconsistent comparator (a < b and b < a)

Spotting AI Sorting Mistakes

Common issues in AI-generated sorting implementations

Use built-in sorting for production code

Questions to Ask AI About Sorting#

When an AI generates a sorting implementation, push it on specifics rather than accepting the first answer:

  1. "What's the worst-case complexity and when does it occur?" — This reveals whether the AI understands pathological inputs (e.g., sorted arrays for naive quick sort).
  2. "Is this sort stable? Does that matter for my use case?" — AI sometimes claims stability without verifying it in the implementation.
  3. "How much extra memory does this use?" — Important for large datasets or memory-constrained environments.
  4. "Would the built-in sort be better here?" — For most production use cases, the answer is yes. Built-in sorts are highly optimized and well-tested. Implement your own only when you have a specific, justified reason.

Summary#

AlgorithmKey Characteristics
Bubble SortSimple, O(n²), stable, best for teaching
Selection SortSimple, O(n²), minimizes swaps
Insertion SortO(n) for nearly sorted, great for small arrays
Merge SortO(n log n) guaranteed, stable, needs O(n) space
Quick SortFast average case, in-place, but O(n²) worst case
Heap SortO(n log n) guaranteed, O(1) space, not stable

Sorting algorithms are one of the most studied problems in computer science, and for good reason — they illustrate core ideas that appear throughout software engineering: recursion, divide-and-conquer, stability, in-place vs. out-of-place computation, and best/average/worst-case analysis.

Understanding these algorithms helps you:

  • Evaluate AI-generated sorting code — you know what questions to ask and what to verify
  • Choose the right algorithm for your specific constraints (data size, memory, stability requirements)
  • Reason about time-space trade-offs — a recurring skill across all of software engineering
  • Recognize patterns that reappear in many other algorithms, from binary search to tree traversal