Sort Basics

Five short pages that build on each other. Nothing needs to be known in advance.

EXPERT entrances

Start with what you want to know

Read from the beginning or jump straight to what you need.

Browse by chapter

Understanding and choosing 1 / 9 1 / 5

What you will learn Learn what sorting means and why order makes data easier to find.

What is sorting?

Putting mixed-up things back into a chosen order is sorting.

Each circle moving above is one of the things being put in order. Here, we call these circles marbles.

Sorting is not only smallest to largest. Largest first, alphabetical order, and newest first are sorting too. We sort people by height, books by title, and photos by date every day.

Line up by height

short → tall

Sort books by title

A → Z

Sort photos by date

newest → oldest

If you search using the same order, you can skip places where the item cannot be. Computers sort data when that makes things easier to find or display.

The finished order can be the same even when the marbles get there in different ways. Watch how they move.

There is more than one way to put things back in order. Let's see how different sorts make progress.

EXPERT

One step deeper: Why do computers sort?

Using the order to search can narrow the range it has to check. Ordering can also group related items. Binary search expects sorted data, databases use ordering, and file systems organize contents by name or date.

Binary search

One comparison against the middle value 5 rules out the whole left half. When the values are scrambled you cannot know that 5 sits in the middle, so binary search does not apply.

Database index (B-tree)

The root holds 30 and 60, so a lookup for 42 only has to follow the middle branch, the one between them. The other two are never opened. An index is this same step stacked many levels deep.

File listing

When you look at a file listing, switching between name order and date order makes different things easy to find. By name you get "the ones starting with m"; by date you get "the newest one".

The value that decides the ordering here (the file name, or the date modified) is called the key. Each thing being put in order is called an element. The marbles at the top of this page are elements, and the number written on each one is its key.

Make a choice

You want to order photos from oldest to newest. Which clue should decide their order?

EXPERT · Think from the conditions

The same product data is sorted by price on screen A and by product name on screen B. What changes between the screens?

Before You Call a Sorting API

We have seen sorting as putting elements into an order that fits a purpose. Now let us look at what to decide before calling a sorting API in application code.

Before choosing an algorithm name, check the result the application needs and the contract of the API. Decide these six items in order to tell whether a standard full sort fits or another tool is the better match.

  1. Do you need a complete order of every item?

    If the result does not need to decide which of every pair of elements comes first, a full sort can be unnecessary work. Look first for the API or data structure that directly matches the result.

    • One minimum or maximum → min / max
    • Only the highest or lowest k → top-k (select only the k items you need)
    • Only a median or a boundary such as the top 10% → selection (find the item at a chosen rank)
    • Repeated lookup over the same data → an index or ordered data structure
    • A database query already guarantees the required order → do not sort it again into the same order
  2. Does the key or comparator define a consistent order?

    Decide whether null comes first or last, how NaN behaves, and which language or region (locale) supplies the rules for ordering text. Decide as well whether items with equal keys stay tied, or whether a second key settles their order.

  3. May the original data be changed?

    If other code refers to the same array, an API that changes the input also changes the order that code sees. Check the signature and documentation to learn whether the input changes or the API returns a new sequence.

  4. Must equal keys keep their original order?

    Requirements such as showing tied scores in signup order, or preserving an earlier stage of a multi-key sort, need a stability guarantee. Read that guarantee from the API instead of inferring it from an algorithm name.

  5. Are memory, comparisons, or key extraction constrained?

    Check not only whether a temporary array fits, but also whether text ordering or key calculation is expensive. An expensive key can be better extracted and stored once instead of recomputed on every comparison.

  6. Are you relying on an API guarantee or today's implementation?

    Correctness properties such as stability, worst-case complexity, and mutation must come from the API contract. An internal implementation such as pdqsort or ipnsort can explain performance, but it is not necessarily a promise that survives future versions.

See the error-prone boundary in code

Which API keeps the input unchanged? Compare JavaScript's `sort` and `toSorted`.

const ordered = items.toSorted(compare);
items.sort(compare);

toSorted returns a new array, while sort rearranges items itself. Looking only at the returned value hides this difference.

What does a sorting API promise?

When choosing a sorting API, check these four parts of its contract. Some APIs leave the order of equal keys unspecified. Others, including APIs named stable_sort, explicitly promise to preserve that order.

Order
A key or comparator defines which item comes first.
Same elements
Nothing is lost or invented; only the order changes.
Equal keys
Stability says whether items with equal keys keep their original relative order.
Result shape
Some APIs mutate the input, while others return a new ordered sequence. One language often has both, as with sort and toSorted in JavaScript or list.sort() and sorted() in Python.

What a comparator must guarantee

Some languages let callers supply the ordering. A key function extracts a value, as in Python's sorted(key=...) or C#'s OrderBy(x => x.Score). A comparator receives two elements and reports which comes first. JavaScript's sort accepts a comparator.

players.sort((a, b) => a.score - b.score);

The language also decides what happens when you pass nothing. JavaScript's Array.prototype.sort converts elements to strings before ordering them when the comparator is omitted, so [1, 2, 10] comes back as [1, 10, 2]. For when you want numeric order, the language lets you pass a comparator.

[1, 2, 10].sort();
[1, 2, 10].sort((a, b) => a - b);

A comparator must give a consistent answer about which element comes first. Check these three rules.

Consistent
Comparing the same two items always gives the same answer. No randomness, no clock, no state that changes while the sort is running. Swapping the arguments has to agree too: it must never report that a precedes b and that b precedes a.
Transitive
If a comes before b and b comes before c, then a comes before c. Floating point error and rounded scores are common ways this breaks.
Ties stay consistent
Two items where neither precedes the other are tied. If a ties b and b ties c, then a must tie c. Comparing with a tolerance (equal when the difference is below a threshold) is the classic way to break this.

A wrong comparator does not only fail to put things in order; it can raise an exception. Java's Arrays.sort and Collections.sort, for example, throw IllegalArgumentException (Comparison method violates its general contract!) when they detect a violation.

Most languages ship a sorting algorithm in their standard library. What stays on the calling side is which key to order by, and whether the comparator you hand over holds to the three rules above.

A 30-second check before calling sort

Decide from the required result and the API contract, not an algorithm name. After answering, compare the source and result as marble rows.

Try the questions Four questions about top-k, mutation, stability, and comparators.
1. Inspect every record, but do not sort them all

You need the 20 highest scores from one million records. Break ties by smaller ID. There is no next page or full export. If an unseen record could rank first, how many records must you inspect, and how many must you put in order? The marbles shrink the same decision to a top three.

2. Preserve the source row

A preview and an audit log share arrival-ordered orders. Only the preview should be amount-ordered. What preserves the audit order?

3. Carry input order through stability

The API guarantees rows arrive in time order. Group by severity while preserving time order within each severity. What is the minimum sufficient key specification?

4. Fix comparator equality

A stable sort still scrambles equal-priority 2a and 2b. The comparator returns -1 even for equal priority. What should be fixed first?

A Row of Boxes, and Sorting Moves

Arrays and Sorting Moves

Where do the marbles go?

A computer remembers the marbles in a row of numbered boxes. This connected row of boxes is called an array.

The numbers make it easy to say “look at box 2” or “switch boxes 1 and 3.” The computer can also pass around the whole row or visit each box in order.

Compare, then move

Many sorts compare marbles and move them when their order is wrong. Repeating this slowly turns a mixed-up row into an ordered one.

Compare

Ask, “Which comes first, 3 or 1?”

Move

If the order is wrong, move the marbles to new places.

EXPERT

What SortVivo observes

SortVivo mainly observes read, write, comparison, swap, and move events as a sort progresses. Some algorithms also use other kinds of work. Counting these operations is how the cost of a sort is measured.

In the figures below, [0] [1] [2] are slot positions. Arrays in most languages count from zero, so the slot called "number 1" in the figure above is [0] inside a program.

Read

Taking the value that sits at a given position

Write

Putting a value into a given position

Compare

Asking which of two keys comes first

Swap

Exchanging where two values sit, leaving the positions themselves untouched

Move

Shifting the values in between aside, then placing the taken value into the gap

The same algorithm may use fewer comparisons but more movement. Its measured cost depends on what you count.

Make a choice

Two marbles are 3, 1. After comparing them, you know 3 is larger. What should happen next to put them in increasing order?

EXPERT · Think from the conditions

You wanted scores in ascending order, but wrote the rule `a.Score > b.Score`. The result is descending order. What should you fix first?

How does each sort make progress?

Different sorts put different parts of the row in order after each step. Watch what falls into place, not only which marble moves.

How it makes progress

Fix two neighbors in the wrong order

Compare neighbors and exchange them when the larger one is on the left.

What changed? That neighboring pair is now in the right order.

Sorts that progress this way:Bubble sort

How it makes progress

Put the next marble into the ordered part

Take the next marble, make room for it in the part that is already in order, and put it there.

What changed? The ordered part is one marble longer. The marble at the end is still waiting.

Sorts that progress this way:Insertion sort

How it makes progress

Choose what belongs next

Search the unfinished part for its smallest marble and place it at the front.

What changed? The smallest marble is now first. We will not move it again.

Sorts that progress this way:Selection sort

How it makes progress

Join two ordered rows

First, put two short rows in order. Compare the front marbles and take the smaller one until the two rows become one.

What changed? Two ordered rows became one ordered row. Only those two rows were joined, not the whole row.

Sorts that progress this way:Merge sort

How it makes progress

Separate around a guide

Choose one marble as the guide, then move values smaller than it to one side and larger values to the other.

The marble chosen as the guide is called the pivot.

What changed? Values are on the correct side of the guide; the two sides are not necessarily sorted yet.

Sorts that progress this way:Quicksort

How it makes progressEXPERT

Build a structure for extraction

Maintain a heap so an extreme value can be found and removed repeatedly.

What changed? The largest value is ready at the root; the rest is only heap-ordered.

Sorts that progress this way:Heapsort

How it makes progressEXPERT

Use the key to choose a destination

A value or digit sends each item to a bucket instead of deciding every position by comparison.

What changed? Items with the same key part are grouped in the same destination. The order inside each destination is not decided yet.

Sorts that progress this way:Bucket sort, LSD Radix sort (b=10)

EXPERT · What Insertion Sort preserves midway

Focus on the ordered range at the start of the row during Insertion Sort.

Choose one answer

During Insertion Sort, what can you say about the leading range?

Compare more (challenge) Carry two ways of making progress to the end, or put a different row in order yourself.

What happens if you keep going?

Every card above showed a single step. Repeat that one step, and how far does a mixed-up row get? Here are two of them, each carried all the way to the end.

Green marks the marbles that are in order so far. They can still move later.

  1. At the start, only the marble on the left is in the ordered part.

Your turn

Now you decide. Use the same move with different numbers: put the next marble into the ordered part. You will choose where it goes.

  1. At the start, only the marble on the left is in the ordered part.

Where does 2 go?

Fast Sorts, Slow Sorts

What happens when we add more marbles?

With only a few marbles, every sort finishes quickly. With many marbles, the number of comparisons and moves can become very different.

How the number of comparisons and moves grows when we add more marbles is called time complexity.

Each way of growing has its own name. Check EXPERT if you're curious.

These growth rates are written with the symbols O, Θ and Ω.

EXPERT

Compare growth with numbers

Time complexity expresses the difference we just saw as a relationship between the input size n and the number of operations. Asymptotic notation does not give the actual number of seconds. It helps us compare the broad trend as more marbles are added.

Which operation you count is decided up front. For sorting it is usually either the number of comparisons or the number of element moves, and the same algorithm can lead to different conclusions depending on which one you counted. Below, comparisons are what is counted unless stated otherwise.

Three Ways to Describe Complexity

How do O, Θ, and Ω differ? O is an upper bound, Θ is a matching bound, and Ω is a lower bound.

O (Big-O)

Upper bound

For sufficiently large inputs, the complexity does not grow faster than this upper bound. Saying QuickSort’s worst case is O(n²) means its cost does not asymptotically grow faster than n².

Θ (Theta)

Tight bound

The growth is pinned from above and from below by the same shape. Merge Sort is barely affected by input order: its best, average, and worst cases are all Θ(n log n).

Ω (Omega)

Lower bound

Ω describes a growth rate the cost does not fall below on large inputs. Comparison sorts need Ω(n log n) comparisons in the worst case. Insertion Sort checks every element even on ordered input, so its best case is Θ(n). Ω is not reserved for best cases.

Big-O Is Used Most Often

Many sorting resources conventionally use Big-O for brevity even when describing a tight growth rate. For example, “QuickSort is O(n log n)” may be shorthand for saying its average case is Θ(n log n).

  • Θ(1) Constant time: the counted operation occurs a fixed number of times regardless of input size, as when reading the first array element once.
  • Θ(n) Linear time: the count grows in proportion to the input size, as when scanning an array once from beginning to end.
  • Θ(n log n) Linearithmic time: greater than linear but far less than quadratic, as in a sort that splits the array and processes all elements at each level.
  • Θ(n²) Quadratic time: the count grows with the square of the input size, as when a double loop examines every pair of elements.

Growth Rate Comparison

A general sort must examine every element, so it requires Ω(n) time. Furthermore, a general sort that determines order through comparisons alone requires Ω(n log n) comparisons in the worst case. Counting operations directly, n² is about 750 times n log₂ n when n = 10,000.

n n n log₂ n
1010~33100
100100~66410,000
1,0001,000~9,9661,000,000
10,00010,000~132,877100,000,000

Why Constants Disappear

Asymptotic notation focuses on the growth rate as input size n increases, so it drops constant multipliers. Counts of 2n and 5n are both tightly Θ(n), and both also fit the same O(n) upper bound. This helps compare broad trends, but it does not capture their difference in actual speed.

Dominant Operations

The overall time complexity of an algorithm is set by whichever operation grows fastest as the input size grows. That operation is called the dominant operation, or the bottleneck. Because complexity can vary depending on the input, it is usually described in terms of best, average, and worst cases.

For example, even if comparisons are O(n), if shift writes are O(n²), the overall complexity is O(n²). As the input grows, the O(n²) work dominates the total cost.

  • Best case: performance on the most favorable input for a given sort, such as an already-sorted array.
  • Average case: the expected performance under a stated input distribution. Sorting analyses often assume that every input permutation is equally likely.
  • Worst case: performance on the most unfavorable input for a given sort.

Same Big-O Does Not Mean Same Performance

HeapSort and QuickSort are both described as O(n log n). HeapSort is Θ(n log n) for every input, while QuickSort averages Θ(n log n) and has a Θ(n²) worst case. Even so, QuickSort can finish sooner because some implementations use the cache better. Big-O does not show this difference.

Make a choice

As you add more marbles, which pattern keeps the computational cost from growing as quickly?

EXPERT · Think from the conditions

Let the input size be n. An algorithm performs Θ(n²) comparisons and Θ(n log n) IndexWrite operations. Each operation costs Θ(1), and no other work grows faster. What is its total time complexity?

The O(n log n) Barrier

General sorting algorithms that determine order through comparisons alone require Ω(n log n) comparisons in the worst case. Therefore, a comparison sort with O(n log n) worst-case time is asymptotically optimal. The stable Merge sort and the unstable Heapsort are equally optimal in this sense.

Start with a decision tree for three elements

Sort three distinct elements A, B, and C using only less-than comparisons. Each answer is Yes or No, and each leaf is the completed order reached by that path.

Three elements have 3! = 6 possible orders, so the decision tree needs six leaves. Some paths in this tree use two comparisons, but handling every input requires three comparisons in the worst case.

In general, n distinct elements have n! possible orders. A binary decision tree of height h has at most 2ʰ leaves, so distinguishing every order requires 2ʰ ≥ n!, or h ≥ ⌈log₂(n!)⌉. This is Θ(n log n), giving a worst-case lower bound of Ω(n log n) comparisons.

To break through this wall, you need information beyond comparisons. Distribution sorts use the values themselves or the shape of the keys to place elements, freeing them from the comparison-based lower bound and enabling sorting faster than O(n log n) under the right conditions. Counting Sort and Radix Sort are the prime examples.

Speed also depends on the shape of the keys. Counting Sort is Θ(n + k) for key range k, and LSD Radix Sort is Θ(n · d) for d digits. Both use keys directly, so they count different work from comparison sorts. A large key range or many digits can make them slower.

The worst-case lower bound for comparison sorting does not change. However, a sort can reduce comparisons and movement by using order already present in the input. Adaptive sorts use this idea.

Make a choice

There are three distinct values and six possible orders. Can one less-than comparison always identify the right order?

Stability and extra memory

Speed is not the only thing to weigh when choosing a sort. How much memory it uses, and whether it keeps the order of equal keys, matter too.

In-place sorting and extra memory

The memory used in addition to the input is the sort's auxiliary space complexity. An in-place sort moves elements within the original array instead of allocating a temporary array proportional to the input size. It usually needs O(1) working storage, although some implementations use about O(log n) memory to record recursive calls. A sort with a temporary array may need O(n) extra memory.

On embedded devices or in memory-constrained environments, allocating O(n) extra memory is often impractical. In such cases, in-place sorts become a strong choice.

Stability

A stable sort preserves the relative order of elements with equal keys. When sorting students by score, a stable sort keeps “Tom” and “Sam” (both 90) in their original order.

This is what pays off when you want to order by two keys. Sort by name first, then sort by score with a stable sort, and within each score the names stay in order. Stacking stable sorts is enough to build a multi-level ordering. An unstable sort loses the first pass, so the two keys have to be handled together inside the comparator instead.

Stable Sort

Equal keys (90) keep their original order: Tom stays before Sam.

Unstable Sort

Equal keys (90) may swap: Sam moved before Tom.

Compare Implementation Variants with Two Questions

Does it keep the order of equal keys? Does it sort without extra memory? Those two answers split the implementation variants into four groups. The amount of extra memory varies by implementation.

Block sort (WikiSort) is stable and in-place, has O(n log n) worst-case time, and uses O(1) extra memory. Timsort is stable but not in-place and uses O(n) extra memory.

Implementation variants arranged by stability and whether they are in-place
In-place (O(1) to O(log n)) Not in-place (see exact amount below)
Stable
Unstable
Properties of the implementation variants in the grid above
Algorithm / variant Stable In-place Extra memory Notes
Bubble sort O(1) Stable & in-place, but slow
Insertion sort O(1) Fast on small inputs
Block sort (WikiSort) O(1) Stable, in-place, and O(n log n), with larger constant costs
Merge sort O(n) O(n) extra memory
Quicksort O(log n) Low extra memory but unstable
Heapsort O(1) O(n log n) worst-case
Timsort O(n) Adaptive + O(n) memory
Powersort O(n) Improves the merge order used by Timsort-style sorts
LSD Radix sort (b=10) O(n+k) Stability required for correctness
Tournament sort O(n) O(n) tournament tree; the tree itself is the point
Patience sort O(n) O(n) piles; also yields the longest increasing subsequence

How to Achieve Stability

Stability is not just a property of an algorithm. It can be built in through design. There are two main strategies.

  • Strategy 1: Use a stable algorithm. The simplest approach: choose an implementation that is inherently stable (Merge Sort, TimSort, PowerSort). No extra effort needed.
  • Strategy 2: Embed the original position in the comparator. Layer stability on top of an unstable sort. Design the comparator as follows: compare by key first; if the keys are equal, compare by original position. This imposes a total order so the algorithm never sees tied keys. Stability is guaranteed by the comparison specification, not the algorithm.

.NET's OrderBy / ThenBy follow strategy 2. Rather than using a stable sort, .NET embeds the original position into the comparison function so the order is fully decided. The sort inside can change, and stability still holds as an API promise.

EXPERT · Think from the conditions

You confirmed that one QuickSort implementation is stable. Can you conclude that another QuickSort implementation is also stable?

How algorithms are built

A sort is built from choices about how to divide a problem and combine the results. Top-down and bottom-up processing, and merge-based and partition-based designs, follow different flows.

Fundamentals Whether code divides top-down or builds bottom-up, and how it merges ordered runs. Most implementations differ along these two choices.

Top-down and Bottom-up

Top-down splits recursively, so the code mirrors the problem structure and handles uneven sizes naturally. Bottom-up starts from small units and merges upward with loops. It avoids both recursive calls and the call stack that records unfinished function calls.

Merge

A merge combines two sorted arrays into one sorted array. By repeatedly taking the smaller of the two front elements, it preserves order throughout. It is a fundamental operation used in Merge Sort and many other algorithms.

Divide and Conquer Merge-based and partition-based forms share a strategy, but place their comparison, movement, and memory costs differently.

Divide-and-conquer is a design method: split the problem, solve each part, and combine the results. In sorting, the code splits into two forms, merge-based and partition-based.

Merge-based

Sort the parts and merge them to form the whole. Implementations may split recursively from the top down, merge small units from the bottom up, or use existing runs in a natural merge strategy.

MergeSort, TimSort, PowerSort

Partition-based

Split the input by a pivot or key, then sort each part independently. A 3-way partition groups equal keys and avoids recursing over them. MSD Radix Sort also divides from higher digits downward. LSD Radix Sort distributes from lower digits upward, so it is not divide-and-conquer.

QuickSort, SampleSort, MSD Radix sort
Make a choice

How does Merge Sort divide the problem and combine the results?

Why implementation changes speed

Algorithms with the same average O(n log n) complexity can run at different speeds. Recursion means that a function calls itself on a smaller problem. A buffer is temporary storage for values used during processing. Changing recursion, buffers, and copying can reduce calls and memory use.

QuickSort and HeapSort both average O(n log n). Given the same random input, which one finishes first? The Sortube race scales each lane by its measured time, so the lane that reaches the end first is the one that actually ran faster. Make your prediction, then compare the two measured times. The result holds for this input, this implementation, and this machine.

Recursion → Iteration Replacing recursion with a loop can reduce function calls and stack use.

Stack use depends on the implementation. Recursive Merge Sort normally uses O(log n), while a naive QuickSort can reach O(n) after badly unbalanced partitions.

Implementation examples: Bottom-up merge sort
Tail Recursion Elimination Recurse only on the smaller side and loop over the larger side to bound stack depth even under skewed partitions.

This keeps stack depth at O(log n), even when partitions are badly unbalanced.

Implementation examples: Quicksort, Quicksort (3-way), Quicksort (Median3), Quicksort (Median9), DualPivot Quicksort, Quicksort (Stable), Quicksort (Bidirectional Stable), Quicksort (Destswap Stable), BlockQuickSort, Introsort and 5 more
Ping-Pong Buffering Alternate source and destination at each level to avoid copying every completed level back to the original array.

Writes between buffers still occur. If the final result lands outside the original array, one final copy is required.

Implementation examples: Pingpong merge sort, Bottom-up merge sort, std::stable_sort (LLVM), Spinsort, Flat stable sort, Quicksort (Destswap Stable)
Copy-Smaller Strategy Buffer only the smaller run so merging needs at most about half an array of temporary storage.

The larger run stays in the original array while the merge proceeds. The temporary buffer is bounded by O(n/2).

Implementation examples: Timsort, Powersort, ShiftSort, Spinsort, Flat stable sort, Glidesort, Driftsort
EXPERT · Think from the conditions

Two Merge Sorts make the same number of comparisons. One copies back to the original array at every level; the other alternates the roles of two buffers. Which can more readily reduce writes?

Adapting to the Input

Pruning and Early Termination

Find processing that is not needed and skip it.

Sortedness Detection When the input is already ordered, detecting that fact lets the algorithm skip the remaining heavy path.

Nearly ordered input can also avoid work when only the disordering elements need repair.

Implementation examples: Bubble sort, Cocktail shaker sort, Odd-even sort, Comb sort, Circle sort, ShiftSort, Spinsort, Flat stable sort, Smoothsort, Introsort and 4 more
Partial Insertion Sort Cutoff Try insertion only while a small move budget can repair the range, then return to the main strategy when it cannot.

Count moves and treat the input as not nearly sorted when the limit is exceeded.

Implementation examples: Introsort, Pattern-defeating quicksort, PDQ sort (branchless)
Early-Out Heuristic When the observed input is a poor fit, switch to a general path before the current strategy degrades further.

The algorithm can monitor disorder or partition imbalance to decide when to switch.

Implementation examples: Introsort, Drop-Merge sort
Range Reduction Find edges already in place and narrow the range that must be merged.

Before merging, use exponential search, which doubles the search distance as 1, 2, 4, 8, and so on, to skip elements already in their final positions. On nearly sorted data, this can greatly reduce the merge range.

Implementation examples: Pingpong merge sort, Bottom-up merge sort, Block sort (WikiSort), Timsort

Pattern Adaptation and Strategy Switching

Techniques that detect structure in the input and choose a strategy accordingly.

Run Detection Reuse ascending and descending runs already present in the input instead of rebuilding every ordered stretch.

Descending runs are reversed, short runs are extended with insertion sort, and the runs are then merged. Only strictly descending runs may be reversed. Reversing across equal values swaps their order and breaks stability.

Implementation examples: Natural merge sort, Timsort, Powersort, ShiftSort, Spinsort, Glidesort, Driftsort, Strand sort
Drop-Based Techniques Set aside a small number of disordering elements, preserve the main ordered flow, and merge them back later.

Only the buffered elements are processed later, then merged back into the main sequence.

Implementation examples: Drop-Merge sort
Hybrid Switching Hand work to a suitable sub-algorithm according to size, recursion depth, and the observed input pattern.

For example, IntroSort normally uses QuickSort, switches to HeapSort when recursion becomes deep, and uses InsertionSort for small arrays.

Implementation examples: Introsort, std::stable_sort (LLVM), Block sort (WikiSort), Timsort, Powersort, Spinsort, Flat stable sort, Glidesort, Driftsort, Quicksort (3-way) and 10 more
3-Way Partitioning Collect values equal to the pivot and avoid extra comparisons and recursion.

The benefit grows with the number of duplicates. When all elements are equal, the work is O(n).

Implementation examples: Quicksort (3-way), Quicksort (Median3), Quicksort (Median9), DualPivot Quicksort, Quicksort (Stable), Pattern-defeating quicksort, PDQ sort (branchless)
Merge Order Optimization With the same runs, choosing which adjacent pair to merge first changes the total comparisons and movement.

PowerSort derives a priority from the boundary between adjacent runs. It avoids merging runs of very different sizes too early and brings the comparison count close to the theoretical lower bound: a count proved impossible to beat in general.

Implementation examples: Powersort, Glidesort, Driftsort
EXPERT · Think from the conditions

An implementation scans from the start and returns immediately when the input is already sorted. What work remains on a sorted input?

Low-Level Optimization

A CPU keeps recently used data in fast caches, predicts which way a branch will go, and overlaps instructions that do not depend on one another. Low-level optimization arranges work to use these caches, predictions, and instruction pipelines well.

Block Partitioning Separate sequential comparison from batched exchange to reduce unpredictable branches and scattered movement.

Read a block, collect the positions to exchange, then perform those exchanges together. Sequential reads also make better use of the cache.

Implementation examples: BlockQuickSort, PDQ sort (branchless)
Adaptive Pivot Selection Use a larger pivot sample as inputs grow to reduce comparisons from unbalanced partitions.

For example, small inputs may choose the median of three candidates, while larger inputs may sample nine. A larger sample reduces the chance of a badly unbalanced partition.

Implementation examples: Driftsort, Quicksort (3-way), Quicksort (Median3), Quicksort (Median9), DualPivot Quicksort, Quicksort (Stable), Quicksort (Bidirectional Stable), BlockQuickSort, Introsort, Introsort (.NET) and 4 more
Galloping (Exponential Search) When one run keeps winning, exponential search skips repeated element-by-element comparisons.

On nearly ordered data, one run can often provide a whole block of values, greatly reducing comparisons.

Implementation examples: Timsort
Sorting Networks for Small Arrays For a small fixed size, a fixed comparison schedule removes loops and data-dependent branches.

For arrays of about 2 to 5 elements, the comparisons can be written directly in code, removing the loop test as well.

Implementation examples: Block sort (WikiSort), Glidesort, Driftsort, Introsort, std::sort (LLVM), Ipnsort
Bidirectional Merging Resolve the front minimum and back maximum independently, creating instruction streams the CPU can overlap.

Write the minimum to the front of the output and the maximum to the back. The two operations do not wait on each other's result, so the CPU can overlap them.

Implementation examples: Glidesort, Driftsort, Quicksort (Bidirectional Stable), Ipnsort
Sentinels (Unguarded Insertion) Place a value that must stop the scan so the inner loop no longer checks the boundary on every move.

A normal insertion loop checks both the array boundary and the value on every move. In a non-leftmost partition, the preceding pivot can stop the scan, leaving only the value comparison.

Implementation examples: Pair insertion sort, Introsort, Pattern-defeating quicksort, PDQ sort (branchless), std::sort (LLVM)
Loop Unrolling Expand several iterations into one body to reduce loop tests and expose more independent instructions.

The expanded operations are often independent, so the CPU can overlap them. A pdqsort variant that reduces branches processes eight iterations of its offset-generation loop at once.

Implementation examples: Driftsort, PDQ sort (branchless), Ipnsort
Bit Tricks & Bit Packing Express key extraction and flags at bit granularity to reduce instructions and the size of working data.

With a power-of-two radix such as 4, 16, or 256, digit extraction can shift the bit pattern left or right and mask off every part except the needed digit. Packing one flag per element from one byte into one bit reduces that storage by about eight times.

Implementation examples: Weak heapSort, LSD Radix sort (b=4), LSD Radix sort (b=256), MSD Radix sort (b=4), American flag sort, Spreadsort
CPU-Friendly Design The same comparison count can take different time because branches and memory access differ.

Comparison count is not the only source of speed. Small-range code, fewer branches, and sequential memory access also matter. Pivot selection is different: it prevents skewed partitions and reduces comparisons. Practical sorts combine several of these techniques.

Specialise small ranges

Because simple routines outperform the general algorithm at small scale, dedicated fast paths reduce constant-factor overhead. (→ Sorting Networks, Hybrid Switching)

Implementation examples: Block sort (WikiSort), Glidesort, Driftsort, Introsort, std::sort (LLVM), Ipnsort, std::stable_sort (LLVM), Timsort, Powersort, Spinsort and 10 more

Reduce branch mispredictions

Irregular branches stall the CPU pipeline. Separating comparison and swap phases and stabilising branch patterns reduces mispredictions. (→ Block Partitioning, Sentinels)

Implementation examples: BlockQuickSort, PDQ sort (branchless), Pair insertion sort, Introsort, Pattern-defeating quicksort, std::sort (LLVM)

Distribute dependencies

Long dependency chains prevent the CPU from filling its pipeline. Multiple cursors and bidirectional processing keep chains short. (→ Bidirectional Merging, Loop Unrolling)

Implementation examples: Glidesort, Driftsort, Quicksort (Bidirectional Stable), Ipnsort, PDQ sort (branchless)

Improve memory locality

Accessing nearby memory together maximises cache utilisation. Block processing and half-array copies keep working data in cache. (→ Block Partitioning, Galloping, Bit Packing)

Implementation examples: BlockQuickSort, PDQ sort (branchless), Timsort, Weak heapSort, LSD Radix sort (b=4), LSD Radix sort (b=256), MSD Radix sort (b=4), American flag sort, Spreadsort
EXPERT · Think from the conditions

Implementation A makes fewer comparisons than B but takes longer on the same input. Is this result possible?

Limited but Practical Techniques

Parallelism divides work across CPU cores or a GPU. SIMD applies one instruction to several values at once. Both work best on large inputs or keys the CPU can compare directly.

Parallelism via GPU and Threads Large inputs can repay startup and synchronization costs, but that tradeoff does not fit a general sort's default path.

Sorting networks such as Bitonic Sort divide well across workers and are also used on GPUs. Java's Arrays.parallelSort and C++ std::sort with an execution policy make parallelism explicit. Small inputs cannot repay the cost of starting and synchronizing workers.

Parallel QuickSort, Parallel Mergesort, Parallel Radix Sort
SIMD Fixed-width keys can be processed several at once, but arbitrary types and caller comparators are a poor fit.

Google Highway's VQSort and Intel's x86-simd-sort are production examples. NumPy 1.25 (2023) sped up np.sort and np.argsort on AVX-512 processors. NumPy 2.0 (2024) adopted x86-simd-sort and Google Highway to accelerate four ordering APIs. Standard sorts accept arbitrary types and comparators, so SIMD is mainly a fast path for fixed-width keys. NumPy 1.25 release notes NumPy 2.0 release notes

specialised high-performance implementations
EXPERT · Think from the conditions

You run the same sort with twice as many CPU cores. Must the execution time be cut in half?

Six ways to compare sorts

Compare a sort by the information it uses, how it makes progress, its design strategy, data and execution structure, guaranteed properties, and implementation techniques. These views reveal differences that one label cannot.

Decision information

Does it learn order through comparisons, or use values, digits, or keys directly? The former is called a comparison sort.

How it makes progress

Does it insert, select, merge, partition, extract from a structure, or distribute?

Design strategy

How the problem becomes smaller. Divide-and-conquer splits it, decrease-and-conquer removes one part at a time, and transform-and-conquer changes its form first.

Data and execution

Array, linked structure, heap, network, in-place storage, or a separate buffer.

Guaranteed properties

Stable (elements with equal keys keep their order), adaptive (the more ordered the input already is, the sooner it finishes), worst-case bounded, fixed schedule, or hybrid.

Implementation techniques

Pivot selection, detecting runs (stretches already in order in the input), small-sort fallback, block processing, and other engineering choices.

Merge and partition are divide-and-conquer examples. Insertion and selection are decrease-and-conquer examples. A heap is a transform-and-conquer example. Recursion versus iteration is a separate choice.

Compare QuickSort and Timsort

QuickSort and Timsort are both comparison sorts. Are they alike in other ways? Compare them across the six dimensions.

Dimension Quicksort Timsort
Decision information Comparison Comparison
How it makes progress Partition Merge
Design strategy Divide and conquer (split in half from the top) Iterative (combine runs from the bottom; there is no dividing step)
Data and execution Array, in place Array with a side buffer (at most half the elements)
Guaranteed properties Unstable. A naive implementation is O(n²) in the worst case Stable and adaptive. O(n log n) even in the worst case
Implementation techniques Pivot selection, insertion sort on short stretches, heapsort fallback when recursion runs deep Run detection, galloping, insertion sort to extend short runs, merge order tuning

They share only one point: both use comparisons to decide order. Their progress, strategy, memory use, and guarantees differ. The label "comparison sort" does not show those differences.

Reading SortVivo's categories

Sorts can be classified by how they make progress, the data they use, or the properties they guarantee.

How the sort progresses

EXCHANGE SELECTION INSERTION MERGE HEAP PARTITION DISTRIBUTION

Data or execution model

NETWORK TREE

Property

ADAPTIVE

Other

JOKE
A traditional vocabulary

Exchange, selection, insertion, merging, and distribution are names you still meet in books and papers. Modern production sorts combine several of them, so one name is often not enough to describe an implementation.

Reference: Donald E. Knuth, The Art of Computer Programming, Volume 3, Section 5.2.

EXPERT · Think from the conditions

QuickSort and Timsort both decide order through comparisons. What follows from this fact alone?

Modern Standard Sorts and How They Change

Why do standard sorts get replaced? In 2022, Go moved the sort that does not promise to preserve equal-key order to pdqsort. In 2024, Rust replaced both its stable sort, which preserves that order, and its unstable sort, which does not promise to preserve it. Each language weighs API needs, speed, memory, and robustness differently.

What matters when choosing a standard sort?

A new implementation may not be better in every way. Standard sorts balance these needs.

  • API contract and compatibility Whether it preserves stability, avoids a breaking change, retains observable equal-key order, and respects behavior existing code depends on.
  • Worst case and robustness Whether complexity stays bounded on skewed or deliberately constructed inputs and has a predictable limit. This also matters for denial-of-service resistance when an attacker controls the input.
  • Real workloads Whether it saves work on ordered stretches already present in the input (runs), duplicate keys, skew, or elements that are expensive to compare in that language.
  • Memory and code size Whether temporary storage, records of recursive calls, and executable size added when generic code is specialized for each type fit the target environment.
  • CPU and specialization Whether it benefits from caches, branches, data movement, fast paths for keys stored in a fixed number of bits such as integers, and types or comparisons known at compile time.
  • Portability and maintenance Whether it works across targets, is tractable to verify, and remains maintainable at standard-library scale.

What is adaptivity?

An input may already contain ordered stretches or many duplicate keys. A sort can use those features to reduce comparisons and movement.

Adaptivity means detecting features of the input and changing the processing. A sort may reuse ordered runs or group duplicate values. API guarantees, memory, and portability are decided separately.

Stable and unstable sorts sit behind separate APIs for a different reason: they let callers choose between different guarantees and costs.

Explain a surprising Sortube result

Compare the input, implementation, and measurements from this race to explain why the times differ. Keep operating requirements and API guarantees separate from that measurement.

Try the questions Four questions about result scope, three observations, deliberately difficult inputs, and API versus implementation.
1. Scope of one result

You predicted the merge-based sort, but this race measured Quick 7.8 ms and Merge 10.6 ms. What can you conclude?

2. Read work beyond comparisons

Quick won despite more comparisons. Data traffic is the amount read from and written to memory. A cache miss means the needed data was not in the CPU's fast cache. A small-sort switch hands a short range to a specialized sort. Which explanation is grounded in this race's measurements?

3. Choose a defended path from requirements

Stability is unnecessary, memory should stay small, and externally supplied patterns must not cause extreme latency. What strategy should you investigate first?

4. Separate contract from current implementation

A source says version X adopted ipnsort. Which design note is safe for callers?

Notable Algorithms

Recent sorts each solve a different problem. Read them by the problem each one set out to solve.

ipnsort and driftsort became Rust's standard sorts in version 1.81. ipnsort is unstable and resists partition imbalance. driftsort is stable and combines run detection, merging, and divide-and-conquer. Rust 1.81 release ipnsort writeup driftsort writeup

PowerSort (Munro and Wild, 2018) chooses the order for merging existing runs. It reduces comparisons while preserving stability and adaptivity. CPython 3.11 adopted it. paper (arXiv) CPython listsort.txt

Glidesort (Orson Peters, 2023) combines run-detecting merges with QuickSort-style partitioning. It is stable, handles duplicate-heavy input and tight memory, and became a basis for driftsort. Implementation source

pdqsort (Orson Peters, 2015) is an unstable sort that changes strategy when partitions stay skewed. The fallback keeps its worst case at O(n log n). Boost, Go, and Zig use it. paper (arXiv) Implementation source

EXPERT · Think from the conditions

You are choosing between ipnsort and driftsort. What should you compare first?

Standard Sorts Across Languages

Language UnstableSort API StableSort API Guarantee Implementation
C qsort() - Not specified The internal algorithm is left to each implementation of the C standard library (libc). qsort_r() was a glibc/BSD extension until POSIX.1-2024 standardized it; the standardized argument order differs from the older BSD one. Official documentation POSIX.1-2024 qsort_r
C++ std::sort std::stable_sort std::sort unspecified; std::stable_sort stable Generally std::sort is introsort-based, std::stable_sort is merge-based. Details are implementation-defined. Official documentation (sort) Official documentation (stable_sort)
C++ (Boost) boost::sort::pdqsort spinsort / flat_stable_sort Chosen by which API you call Offers pdqsort, spinsort, and flat_stable_sort under their algorithm names. Implementation source
Rust slice::sort_unstable() slice::sort() sort_unstable unstable; sort stable Since Rust 1.81 (2024), unstable uses ipnsort and stable uses driftsort. Release notes Implementation source (ipnsort) Implementation source (driftsort)
Go slices.Sort() / sort.Sort() slices.SortStableFunc() / sort.SliceStable() Chosen by which API you call Go 1.19 (2022) rewrote the sort algorithm to pdqsort. The slices package became standard in Go 1.21 (2023). Stable sort uses SymMerge. Release notes Implementation source (pdqsort) Implementation source (SymMerge)
Zig std.sort.pdq / std.mem.sortUnstable std.sort.block / std.mem.sort Chosen by which API you call Per release notes: unstable is pdqsort, stable is block sort. Release notes Implementation source (pdqsort) Implementation source (Block Sort)
Java (primitives) Arrays.sort(...) - Not applicable; equal primitives are indistinguishable Dual-Pivot Quicksort, stated in the Implementation Note. Official documentation Implementation source
Java (objects) - Arrays.sort(Object[]) Stable, required by the spec Implementation Note states TimSort. Official documentation Implementation source
.NET / C# Array.Sort() Enumerable.OrderBy() / ThenBy() Array.Sort unstable; OrderBy / ThenBy stable Array.Sort() is an introspective sort. OrderBy / ThenBy is QuickSort, deciding equal keys by their original position. sort src LINQ src
Python - list.sort() / sorted() Stable A merge sort derived from TimSort that uses the runs already in the input. Since Python 3.11, merge order is chosen with the Powersort strategy. Official documentation Design discussion Code change Implementation source (Timsort 3.10) Implementation source (PowerSort 3.14)
JavaScript - Array.prototype.sort() Stable since ES2019 Engine-dependent; V8 uses TimSort. MDN V8 Implementation source (V8)
Swift - Array.sort() / sorted() Stable, documented by SE-0372 in Swift 5.8 TimSort-based implementation. Official documentation Implementation source
Kotlin - sort() / sortedArray() / sortedBy() Stable Varies by target. On JVM, delegates to java.util.Arrays.sort. Official documentation Implementation source (JavaScript)
Dart List.sort() - Not guaranteed Dual-Pivot Quicksort. package:collection also provides Merge Sort. Official documentation Implementation source
PHP 8+ - sort() / usort() Stable since PHP 8.0 QuickSort-based with InsertionSort for 16 elements or fewer, stabilized via an original-order fallback key. Official documentation Implementation source
Ruby Array#sort - Not guaranteed Delegates to the platform's qsort_r / qsort_s when available; otherwise uses a built-in QuickSort. Official documentation Implementation source

As of 2026-03. API details come from official specifications and documentation. Implementation details come from released source code and release notes. Internal implementations may change.

EXPERT · Think from the conditions

A runtime update changes the internal sorting implementation. Your application requires equal-key elements to keep their original order. What should you check first?

Why Each Language Chose This Sort

Languages give different priority to API guarantees, compatibility, speed, and memory.

C
C standardizes a portable qsort() interface and leaves the implementation to each platform. It does not specify the internal algorithm or the order of equal values. Official documentation
C++
C++ specifies complexity and results, then leaves the concrete algorithm to each implementation. Implementations can change while preserving the API contract. Official documentation (sort) Official documentation (stable_sort)
C++ (Boost)
Boost.Sort exposes APIs named after concrete algorithms. Callers can select an implementation from properties such as stability and resistance to difficult inputs. Implementation source
Rust
Rust 1.81 changed sorting to improve standard-library speed and quality. Its release notes report substantial gains for sorting and partial selection, which finds only the elements at needed ranks. Release notes Official documentation
Go
Go's unstable path keeps O(n log n) even on skewed inputs. Its stable path uses SymMerge to limit extra memory while remaining straightforward to verify. Implementation source Implementation notes
Zig
Zig added a faster API for callers that do not need stability while preserving the existing stable, in-place guarantee. Separate APIs let callers choose between guarantees and speed. Release notes Design discussion #15388 Design discussion #11117
Java (primitives)
Primitive arrays do not distinguish equal values, so Java can prioritize speed over stability. Its documentation describes Dual-Pivot Quicksort as generally faster than the older one-pivot form. Official documentation
Java (objects)
Java guarantees stability for object arrays. TimSort fits that contract and can reduce comparisons on partially ordered input. Official documentation
.NET / C#
Some code depends on the deterministic order of equal values produced by .NET Array.Sort(). Changing to another unstable implementation can therefore break compatibility. Design discussion Code change
Python
Python 3.11 kept stability and adaptivity but changed the merge order to PowerSort's policy. The goal was fewer comparisons. Implementation notes Design discussion
JavaScript
ECMAScript specifies observable results and leaves the algorithm to each engine. When ES2019 required stability, engines such as V8 moved to stable implementations. Official documentation Implementation notes
Swift
Swift turned the already stable behavior of sort() into a documented API guarantee. Callers can now rely on the order of equal keys. Specification proposal SE-0372 Developer discussion
Kotlin
Kotlin keeps one stability guarantee while delegating implementation to JVM, JavaScript, and Native. This reuses each platform's standard facilities. Official documentation
Dart
Dart's List.sort() does not guarantee stability and uses an in-place Dual-Pivot Quicksort. Callers that need stability use a different API. Implementation source
PHP 8+
PHP 8.0 kept its existing sort and broke ties by original position. This added stability with a small implementation change. Change proposal
Ruby
Ruby uses an operating-system sort function when available and a built-in implementation otherwise. Portability takes priority over one uniform algorithm. Implementation source
EXPERT · Think from the conditions

An official release note says a new implementation speeds up common inputs. Your own benchmark shows one input becoming faster. Which explanation is safe to publish?

Explore More

The ways of sorting you saw have names

These are the names you will see in the Visualizer and the quiz. Pick one to follow its movement step by step.

Make a choice

You want to change the input yourself and watch a sort advance one step at a time. Where should you go next?

EXPERT · Think from the conditions

You want to create an adversarial input and inspect Compare and IndexWrite one step at a time. Where should you go next?

Sorting is
a spectacle.
Algorithms at work, in a daily vertical feed. Watch, play, challenge — and go deeper when you're ready.
Loading 0%
An unhandled error has occurred. Reload 🗙