Skip to content

Sorting

Sorting is one of the most studied problems in computer science. Not because arranging numbers in order is interesting by itself - but because sorted data makes every other problem easier.

This guide covers the theory behind sorting, why so many algorithms exist, and the three foundational sorting algorithms every developer must know.

Sorting means rearranging elements into a specific order.

Most common:

  • Ascending: small to large - [1, 3, 5, 7]
  • Descending: large to small - [7, 5, 3, 1]

Sorting does not change the values - only the positions.

Input: [7, 3, 5, 1]
Output: [1, 3, 5, 7]

If sorting was just about making data look neat, it would not be important. Sorting exists because it unlocks faster algorithms on the data.

Unsorted data:

[40, 10, 30, 20]

Finding 30 means checking every element - O(n) time.

Sorted data:

[10, 20, 30, 40]

Now you can use Binary Search - O(log n) time.

That is a massive difference for large datasets.

After sorting, problems like these become much easier:

  • Find duplicates
  • Find the k-th smallest element
  • Merge two datasets
  • Two-pointer problems
  • Interval problems

That is why so many DSA solutions start with: “First, sort the array…”

These algorithms compare pairs of elements to decide their order.

Is 5 less than 7? -> yes -> keep this order

The Mathematical Limit: No comparison-based sorting algorithm can ever be faster than O(n log n) in the worst case. This is proven mathematically.

Why? Because every comparison gives only 1 bit of information (yes/no), and sorting n elements requires at least log₂(n!) bits - which works out to n log n.

So Merge Sort, Quick Sort, and Heap Sort are all theoretically as good as it gets for comparison-based sorting.

These algorithms do not compare elements. Instead they:

  • Use element values as positions
  • Count occurrences
  • Work on digit by digit

Examples: Counting Sort, Radix Sort, Bucket Sort.

They can beat O(n log n) - sometimes O(n) - but only when:

  • Values are integers
  • The range of values is limited and known
  • Data has special structure

This is one of the most common interview theory questions. The answer is: real-world constraints vary.

There is no single best sorting algorithm. The best choice depends on:

SizeApproach
Small (< 20 elements)Simple algorithms (Insertion Sort)
LargeEfficient algorithms (Merge Sort, Quick Sort)
Data StateEffect on Algorithms
Already sortedInsertion Sort becomes O(n)
Reverse sortedSome algorithms hit their worst case
RandomAverage-case behavior matters
SituationBest Choice
Limited RAMIn-place sorting (O(1) extra space)
Memory availableOut-of-place sorting (easier, often stable)
NeedUse
Order of equal elements must be preservedStable sort
Order does not matterAny sort
DataBest Type
Small integers with known rangeNon-comparison (Counting Sort)
Large values, strings, objectsComparison sort

This is very important and often tested.

A sort is stable if equal elements keep their original relative order after sorting.

Example - sorting by the number:

Input: [(A, 5), (B, 5), (C, 3)]
index 0 index 1 index 2

Both A and B have the value 5. After sorting by number:

Stable result:

[(C, 3), (A, 5), (B, 5)]

A still comes before B - same as the original.

Unstable result:

[(C, 3), (B, 5), (A, 5)]

B and A swapped - original relative order was lost.

Imagine sorting a list of employees first by name (alphabetically), then by salary.

If the second sort (by salary) is stable, employees with the same salary keep their alphabetical order from the first sort. If it is unstable, the name ordering is destroyed.

This matters whenever you are sorting objects with multiple fields.

AlgorithmStable?
Bubble SortYes
Insertion SortYes
Merge SortYes
Selection SortNo
Quick SortNo (default)
Heap SortNo

The algorithm sorts the array using the same array - no extra array is created.

Extra space = O(1)
[4, 2, 1, 3]
↕ swaps happen inside the same array
[1, 2, 3, 4]

Pros: Memory efficient. Cons: Harder to make stable.

The algorithm uses a separate array to build the sorted result.

Extra space = O(n)
Original: [4, 2, 1, 3]
Helper: [1, 2, 3, 4] ← built separately

Pros: Easier to write, easier to make stable. Cons: Uses more memory.

ConditionChoice
Memory is tightIn-place
Stability is requiredOut-of-place (usually easier)
Large data, memory availableEither, depending on algorithm

An algorithm is adaptive if it runs faster when the input is already partially sorted.

Nearly sorted: [1, 2, 3, 4, 6, 5]

An adaptive algorithm recognizes this and does less work.

  • Insertion Sort is adaptive - O(n) on already sorted data
  • Selection Sort is NOT adaptive - always O(n²) regardless

Adaptive algorithms are useful in real-time systems where data keeps coming in mostly sorted.

The entire data fits in RAM. All algorithms in this guide are internal sorting.

The data is too large for RAM - it lives on disk. The algorithm must minimize disk reads and writes, which are much slower than memory access.

Used in databases and file systems.

Every developer must know these three. They are slow for large data but fundamental - and they appear in interviews constantly.

Compare two neighboring elements at a time. If they are in the wrong order, swap them. Keep doing this until nothing swaps.

The largest element “bubbles up” to the end with each pass - that is where the name comes from.

Bubble Sort Visualization

Array: [5, 1, 4, 2]

Pass 1 - compare neighbors left to right:

(5, 1) -> wrong order -> swap -> [1, 5, 4, 2]
(5, 4) -> wrong order -> swap -> [1, 4, 5, 2]
(5, 2) -> wrong order -> swap -> [1, 4, 2, 5]

After pass 1: 5 is at the end - its correct place.

Pass 2:

(1, 4) -> correct order -> no swap
(4, 2) -> wrong order -> swap -> [1, 2, 4, 5]

After pass 2: 4 and 5 are in place.

Pass 3:

[1, 2, 4, 5] -> no swaps needed -> done
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, n - i - 1): # last i elements already sorted
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # optimization - stop early if already sorted
break

The swapped flag is the optimization that makes best case O(n) - if no swaps happen in a pass, the array is sorted and we stop early.

CaseTimeWhy
BestO(n)Already sorted - no swaps, one pass
AverageO(n²)Most elements need moving
WorstO(n²)Reverse sorted

Space: O(1) - in-place. Stable: Yes - equal elements never swap.

Find the smallest element in the unsorted portion. Put it at the front. Repeat for the rest.

Instead of many swaps like Bubble Sort, Selection Sort does exactly one swap per pass.

Selection Sort Visualization

Array: [5, 1, 4, 2]

Pass 1 - find minimum in whole array:

Minimum is 1 (at index 1)
Swap with index 0 -> [1, 5, 4, 2]

Pass 2 - find minimum in remaining [5, 4, 2]:

Minimum is 2 (at index 3)
Swap with index 1 -> [1, 2, 4, 5]

Pass 3 - remaining [4, 5] already sorted.

[1, 2, 4, 5] ✓
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_index = i # assume current position is minimum
# find the actual minimum in the rest of the array
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
# swap minimum into correct position
arr[i], arr[min_index] = arr[min_index], arr[i]
CaseTimeWhy
BestO(n²)Always scans remaining elements
AverageO(n²)Same
WorstO(n²)Same

Space: O(1) - in-place. Stable: No - swapping can change relative order of equal elements. Adaptive: No - always O(n²) regardless of input.

Think about how you sort playing cards in your hand. You pick one card, find where it fits among the cards already in your hand, and slide it in.

That is exactly Insertion Sort. Take one element, and insert it into the correct position among the already-sorted portion.

Insertion Sort Visualization

Array: [5, 1, 4, 2]

Start: [5] | 1 4 2 (sorted | unsorted)
Step 2: [1, 5] | 4 2 (1 inserted before 5)
Step 3: [1, 4, 5] | 2 (4 inserted between 1 and 5)
Step 4: [1, 2, 4, 5] (2 inserted between 1 and 4)
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i] # the element we want to insert
j = i - 1
# shift elements right to make space for key
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key # insert key in correct position
CaseTimeWhy
BestO(n)Already sorted - no shifting needed
AverageO(n²)Elements need shifting
WorstO(n²)Reverse sorted - max shifting

Space: O(1) - in-place. Stable: Yes. Adaptive: Yes - fastest of the three on nearly sorted data.

AlgorithmBestAverageWorstSpaceStableAdaptive
Bubble SortO(n)O(n²)O(n²)O(1)YesYes
Selection SortO(n²)O(n²)O(n²)O(1)NoNo
Insertion SortO(n)O(n²)O(n²)O(1)YesYes
SituationBest Choice
Learning / teachingBubble Sort
Swaps are expensiveSelection Sort
Data is nearly sortedInsertion Sort
Need a stable sortBubble or Insertion
Any real production codeMerge Sort or Quick Sort (next topic)

The Interview Framework - How to Answer “Which Sort?”

Section titled “The Interview Framework - How to Answer “Which Sort?””

Never answer sorting algorithm questions directly. Use this structure:

  1. Ask about input size - small or large?

  2. Ask about memory - do we have extra space available?

  3. Ask about stability - do equal elements need to keep their order?

  4. Ask about data distribution - is it random, nearly sorted, or reverse sorted?

  5. Ask about data range - are values integers in a known range?

  6. Now give your answer - based on the constraints, pick the right algorithm and explain why.

Bubble Sort

Compares neighbors, swaps if wrong order. O(n²) average. Stable. Good for learning and nearly sorted data with the swapped optimization.

Selection Sort

Finds minimum, swaps once per pass. O(n²) always. Not stable, not adaptive. Use when swaps are expensive.

Insertion Sort

Inserts one element at a time into sorted portion. O(n) best case. Stable and adaptive. Best for small or nearly sorted data.

Stability

Stable = equal elements keep original order. Bubble and Insertion are stable. Selection is not.

In-Place

All three use O(1) extra space - no extra array needed.

No Single Best

The best sorting algorithm depends on input size, memory, stability needs, and data distribution. Always ask first.

  • Sorting makes searching O(log n) instead of O(n)
  • Comparison-based sorting cannot beat O(n log n) - mathematical law
  • Non-comparison sorting can be faster but needs special data conditions
  • No single best algorithm - always choose based on constraints