Skip to content

Sorting Algorithms

Sorting algorithms arrange elements in a specific order. Understanding different sorting methods and their trade-offs is essential for algorithm design.

Interactive Sorting Visualizer

Watch each algorithm sort an array step-by-step. Adjust speed and array size, and track comparisons and swaps in real time.

Sorting Algorithm Visualizer

ACTIONS
SETTINGS
Size30
Speed50
Comparisons: 0Swaps: 0
Best: O(n)Average: O(n²)Worst: O(n²)Space: O(1)Stable: Yes
DefaultComparingSwappingSortedPivot

Algorithm Race

Compare sorting algorithms head-to-head on the same data. See which one wins!

Algorithm Race

Compare sorting algorithms head-to-head with real performance metrics

Array Size
500
Data Pattern
Select algorithms and click "Start Race" to compare their performance

Complexity Comparison

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
Counting SortO(n + k)O(n + k)O(n + k)O(n + k)Yes
Radix SortO(d(n + k))O(d(n + k))O(d(n + k))O(n + k)Yes

n = number of elements, k = range of values, d = number of digits

Comparison-Based Sorting

Bubble Sort

Repeatedly swap adjacent elements if out of order.

Pass 1: [5, 3, 8, 4, 2] → [3, 5, 4, 2, 8]
Pass 2: [3, 5, 4, 2, 8] → [3, 4, 2, 5, 8]
Pass 3: [3, 4, 2, 5, 8] → [3, 2, 4, 5, 8]
Pass 4: [3, 2, 4, 5, 8] → [2, 3, 4, 5, 8]
def bubble_sort(arr):
"""
Time: O(n²), Space: O(1), Stable: Yes
"""
n = len(arr)
for i in range(n):
swapped = False
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # Optimization: stop if sorted
break
return arr

Selection Sort

Find minimum element and place at beginning.

[5, 3, 8, 4, 2] → Find min (2), swap with first
[2, 3, 8, 4, 5] → Find min in rest (3), already in place
[2, 3, 8, 4, 5] → Find min in rest (4), swap with 8
[2, 3, 4, 8, 5] → Find min in rest (5), swap with 8
[2, 3, 4, 5, 8] → Sorted
def selection_sort(arr):
"""
Time: O(n²), Space: O(1), Stable: No
"""
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr

Insertion Sort

Build sorted array one element at a time.

[5, 3, 8, 4, 2]
[5] | 3, 8, 4, 2 → Insert 3: [3, 5]
[3, 5] | 8, 4, 2 → Insert 8: [3, 5, 8]
[3, 5, 8] | 4, 2 → Insert 4: [3, 4, 5, 8]
[3, 4, 5, 8] | 2 → Insert 2: [2, 3, 4, 5, 8]
def insertion_sort(arr):
"""
Time: O(n²), Space: O(1), Stable: Yes
Best for small arrays and nearly sorted data.
"""
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr

Merge Sort

Divide array in half, sort each half, merge sorted halves.

[5, 3, 8, 4, 2, 7, 1, 6]
/ \
[5, 3, 8, 4] [2, 7, 1, 6]
/ \ / \
[5, 3] [8, 4] [2, 7] [1, 6]
/ \ / \ / \ / \
[5] [3] [8] [4] [2] [7] [1] [6]
\ / \ / \ / \ /
[3, 5] [4, 8] [2, 7] [1, 6]
\ / \ /
[3, 4, 5, 8] [1, 2, 6, 7]
\ /
[1, 2, 3, 4, 5, 6, 7, 8]
def merge_sort(arr):
"""
Time: O(n log n), Space: O(n), Stable: Yes
"""
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# In-place merge sort
def merge_sort_inplace(arr, left, right):
if left < right:
mid = (left + right) // 2
merge_sort_inplace(arr, left, mid)
merge_sort_inplace(arr, mid + 1, right)
merge_inplace(arr, left, mid, right)

Quick Sort

Pick a pivot, partition around it, recursively sort partitions.

[5, 3, 8, 4, 2, 7, 1, 6] pivot=5
↓ partition
[3, 4, 2, 1] [5] [8, 7, 6]
↓ ↓
[1, 2, 3, 4] [5] [6, 7, 8]
def quick_sort(arr):
"""
Time: O(n log n) avg, O(n²) worst, Space: O(log n), Stable: No
"""
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# In-place quick sort (more efficient)
def quick_sort_inplace(arr, low, high):
if low < high:
pivot_idx = partition(arr, low, high)
quick_sort_inplace(arr, low, pivot_idx - 1)
quick_sort_inplace(arr, pivot_idx + 1, high)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1

Heap Sort

Build max-heap, repeatedly extract maximum.

Heap Sort Process:
1. Build max-heap from array
2. Swap root (max) with last element
3. Reduce heap size and heapify
4. Repeat until heap is empty
def heap_sort(arr):
"""
Time: O(n log n), Space: O(1), Stable: No
"""
n = len(arr)
# Build max-heap
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# Extract elements one by one
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, i, 0)
return arr
def heapify(arr, n, root):
largest = root
left = 2 * root + 1
right = 2 * root + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != root:
arr[root], arr[largest] = arr[largest], arr[root]
heapify(arr, n, largest)

Non-Comparison Sorting

Counting Sort

Count occurrences of each value (for small range of integers).

Counting Sort Example: arr = [4, 2, 2, 8, 3, 3, 1]
Step 1: Count occurrences (range 1-8)
count = [1, 2, 2, 1, 0, 0, 0, 1]
1 2 3 4 5 6 7 8
Step 2: Cumulative count
count = [1, 3, 5, 6, 6, 6, 6, 7]
Step 3: Place elements using cumulative count
Output: [1, 2, 2, 3, 3, 4, 8]
def counting_sort(arr):
"""
Time: O(n + k), Space: O(n + k), Stable: Yes
k = range of values
"""
if not arr:
return arr
min_val, max_val = min(arr), max(arr)
range_size = max_val - min_val + 1
count = [0] * range_size
output = [0] * len(arr)
# Count occurrences
for num in arr:
count[num - min_val] += 1
# Cumulative count
for i in range(1, len(count)):
count[i] += count[i - 1]
# Build output (traverse in reverse for stability)
for i in range(len(arr) - 1, -1, -1):
num = arr[i]
count[num - min_val] -= 1
output[count[num - min_val]] = num
return output

Radix Sort

Sort by each digit, from least to most significant.

Radix Sort Example: arr = [170, 45, 75, 90, 802, 24, 2, 66]
Sort by 1s digit: [170, 90, 802, 2, 24, 45, 75, 66]
Sort by 10s digit: [802, 2, 24, 45, 66, 170, 75, 90]
Sort by 100s digit:[2, 24, 45, 66, 75, 90, 170, 802]
def radix_sort(arr):
"""
Time: O(d(n + k)), Space: O(n + k), Stable: Yes
d = number of digits, k = base (10)
"""
if not arr:
return arr
max_val = max(arr)
exp = 1
while max_val // exp > 0:
counting_sort_by_digit(arr, exp)
exp *= 10
return arr
def counting_sort_by_digit(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for num in arr:
digit = (num // exp) % 10
count[digit] += 1
for i in range(1, 10):
count[i] += count[i - 1]
for i in range(n - 1, -1, -1):
digit = (arr[i] // exp) % 10
count[digit] -= 1
output[count[digit]] = arr[i]
for i in range(n):
arr[i] = output[i]

When to Use Which Algorithm

ScenarioBest Choice
Small array (n < 50)Insertion Sort
Nearly sortedInsertion Sort
Guaranteed O(n log n)Merge Sort
General purposeQuick Sort
Limited memoryHeap Sort
Known small rangeCounting Sort
Large integersRadix Sort
Stability requiredMerge Sort

Practice Problems

Easy

ProblemPatternCompanies
Sort an ArrayImplementationAmazon
Merge Sorted ArrayTwo PointersAmazon, Microsoft
Squares of Sorted ArrayTwo PointersAmazon, Google

Medium

ProblemPatternCompanies
Sort ColorsDutch National FlagAmazon, Microsoft
Kth Largest ElementQuick SelectAmazon, Google, Meta
Merge IntervalsSort + MergeAmazon, Google
Meeting Rooms IISort + HeapGoogle, Meta

Hard

ProblemPatternCompanies
Count of Smaller NumbersMerge SortGoogle, Amazon
Reverse PairsMerge SortGoogle
Maximum GapBucket SortAmazon

Key Takeaways

  1. O(n²) algorithms - Simple but slow; use for small/nearly sorted data
  2. O(n log n) comparison sorts - Optimal for general purpose
  3. Quick Sort - Fastest in practice, but O(n²) worst case
  4. Merge Sort - Guaranteed O(n log n), stable, but uses O(n) space
  5. Non-comparison sorts - O(n) possible with constraints

Next Steps