Sorting Foundations
7 concept walkthroughs, each with a worked explanation and an interactive visualization, before you start solving problems in this area.
What is Sorting?
Sorting is the process of arranging items in a specific order: usually from smallest to largest (ascending) or largest to smallest (descending). Think of organizing books on a shelf by height, or arranging playing cards in your hand from lowest to highest.
The intuition
Imagine you have a messy pile of numbered cards. Sorting means rearranging them so you can quickly find any card. Without sorting, finding a specific card requires checking every single one. With sorting, you can use smarter search techniques!
- Sorting arranges elements in a specific ORDER
- Two main orders: ASCENDING (1,2,3...) or DESCENDING (9,8,7...)
- Sorted data enables FASTER searching (binary search)
- Different algorithms have different TRADE-OFFS
- Some algorithms work better for nearly-sorted data
When to use it
- When you need to search data frequently
- When preparing data for display to users
- When finding duplicates or patterns
- When merging multiple data sources
A real-world analogy
Think of a library. Books are sorted by category, then by author name. This organization lets you find any book quickly instead of searching randomly through thousands of books!
Bubble Sort
Bubble Sort works by repeatedly stepping through the list, comparing adjacent elements, and swapping them if they're in the wrong order. The largest elements 'bubble up' to the end of the list, like air bubbles rising to the surface of water.
The intuition
Imagine you're in a line of people sorted by height. You compare yourself with the person next to you. If you're taller, you swap places. Keep doing this until the tallest person reaches the end. Then repeat for the remaining people!
- Compare ADJACENT elements and swap if needed
- Largest elements 'BUBBLE UP' to the end
- Each pass guarantees ONE element is in place
- Simple but SLOW for large datasets
- Great for learning but rarely used in practice
Time complexity
| Best | O(n) - when already sorted |
|---|---|
| Average | O(n²) |
| Worst | O(n²) |
Space complexity: O(1) - in-place sorting
When to use it
- Learning purposes - easiest to understand
- Very small datasets (< 50 elements)
- Nearly sorted arrays (with optimization)
- When simplicity matters more than speed
A real-world analogy
Like bubbles in soda rising to the top! Heavy (larger) elements sink to their correct position while lighter ones bubble up.
for i from 0 to n-1:
for j from 0 to n-i-1:
if arr[j] > arr[j+1]:
swap(arr[j], arr[j+1])
Selection Sort
Selection Sort divides the array into sorted and unsorted parts. It repeatedly finds the MINIMUM element from the unsorted part and moves it to the end of the sorted part. You're literally 'selecting' the smallest remaining element each time.
The intuition
Imagine picking players for a team. Each round, you look at all remaining players and SELECT the best one. After each selection, that player joins your team (sorted part), and you repeat with the remaining players.
- Find the MINIMUM element in unsorted part
- SWAP it with the first unsorted element
- Sorted part GROWS from left to right
- Makes minimum number of SWAPS (O(n))
- Always O(n²) comparisons regardless of input
Time complexity
| Best | O(n²) |
|---|---|
| Average | O(n²) |
| Worst | O(n²) |
Space complexity: O(1) - in-place sorting
When to use it
- When memory writes are expensive
- Small datasets where simplicity matters
- When you need minimum number of swaps
- Educational purposes
A real-world analogy
Like organizing a hand of cards by repeatedly finding the lowest card and moving it to the front of your 'sorted' pile.
for i from 0 to n-1:
minIndex = i
for j from i+1 to n:
if arr[j] < arr[minIndex]:
minIndex = j
swap(arr[i], arr[minIndex])
Insertion Sort
Insertion Sort builds the sorted array one element at a time. For each new element, it finds the correct position in the already-sorted part and INSERTS it there, shifting other elements as needed. Like sorting playing cards in your hand!
The intuition
When you're dealt cards one by one, you insert each new card into its correct position among the cards you already hold. You don't re-sort all cards: you just slide the new one into place!
- Build sorted array ONE element at a time
- Take next element and INSERT into correct position
- SHIFT elements to make room for insertion
- Very efficient for NEARLY SORTED data
- Excellent for SMALL datasets and online sorting
Time complexity
| Best | O(n) - when already sorted |
|---|---|
| Average | O(n²) |
| Worst | O(n²) - when reverse sorted |
Space complexity: O(1) - in-place sorting
When to use it
- Small datasets (typically < 50 elements)
- Nearly sorted arrays - very efficient!
- Online sorting (data arrives one at a time)
- When stability is required
- As part of hybrid algorithms (like Timsort)
A real-world analogy
Exactly like sorting playing cards in your hand! You pick up each card and slide it into its correct position among the cards you're already holding.
for i from 1 to n-1:
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j+1] = arr[j] // shift right
j = j - 1
arr[j+1] = key // insert
Merge Sort
Merge Sort uses the 'Divide and Conquer' strategy. It divides the array into two halves, recursively sorts each half, and then MERGES the sorted halves back together. The magic happens in the merge step, combining two sorted arrays is very efficient!
The intuition
Imagine sorting a deck of cards by splitting it in half, sorting each half separately (by splitting again and again), and then merging the sorted halves back together. Merging two sorted piles is easy. Just always take the smaller card from the top of either pile!
- DIVIDE array into two halves
- CONQUER by recursively sorting each half
- MERGE the sorted halves together
- Guaranteed O(n log n) - always fast!
- Requires extra space for merging
Time complexity
| Best | O(n log n) |
|---|---|
| Average | O(n log n) |
| Worst | O(n log n) |
Space complexity: O(n) - needs extra array for merging
When to use it
- When guaranteed O(n log n) is needed
- Large datasets where stability matters
- External sorting (data on disk)
- When memory is not a constraint
- Linked list sorting
A real-world analogy
Like organizing a library by sections. Split books into genres, sort each genre, then merge genre sections into one organized library. Merging is easy because each genre is already sorted!
mergeSort(arr, left, right):
if left < right:
mid = (left + right) / 2
mergeSort(arr, left, mid)
mergeSort(arr, mid+1, right)
merge(arr, left, mid, right)
Quick Sort
Quick Sort also uses 'Divide and Conquer', but differently. It picks a PIVOT element and partitions the array so all smaller elements go left and larger go right. Then it recursively sorts the left and right parts. The partitioning is the key step!
The intuition
Imagine organizing people by height. Pick one person as the 'pivot'. Everyone shorter goes to the left, everyone taller goes to the right. Now the pivot is in the correct position! Repeat this process for the left and right groups.
- Pick a PIVOT element
- PARTITION: smaller elements left, larger right
- Pivot ends up in its FINAL position
- Recursively sort left and right partitions
- Fastest in practice for most cases
Time complexity
| Best | O(n log n) |
|---|---|
| Average | O(n log n) |
| Worst | O(n²) - bad pivot choices |
Space complexity: O(log n) - recursive call stack
When to use it
- General-purpose sorting - usually fastest
- When average performance matters most
- In-memory sorting of arrays
- When space efficiency is important
- Most standard library sort implementations
A real-world analogy
Like organizing a group photo by height. Pick someone in the middle, put shorter people on their left, taller on their right. That person is now in the right spot! Repeat for each group.
quickSort(arr, low, high):
if low < high:
pivotIndex = partition(arr, low, high)
quickSort(arr, low, pivotIndex - 1)
quickSort(arr, pivotIndex + 1, high)
partition(arr, low, high):
pivot = arr[high]
i = low - 1
FOR j FROM low TO high - 1:
IF arr[j] < pivot:
i = i + 1
swap arr[i] and arr[j]
swap arr[i + 1] and arr[high]
return i + 1
Which Sorting Algorithm to Use?
Each sorting algorithm has its strengths. For small arrays, simple algorithms like Insertion Sort work great. For large arrays, Merge Sort or Quick Sort are better. The 'best' algorithm depends on your data size, whether it's nearly sorted, and memory constraints.
The intuition
There's no single 'best' sorting algorithm, it depends on the situation! It's like choosing transportation: walking is fine for short distances, but you'd take a car for longer trips and a plane for cross-country travel.
- Small arrays (< 50): Insertion Sort is often fastest
- Large arrays: Quick Sort or Merge Sort
- Nearly sorted data: Insertion Sort shines
- Need guaranteed time: Merge Sort (always O(n log n))
- Memory matters: Quick Sort uses less space
- Stability needed: Merge Sort or Insertion Sort
When to use it
- Consider data SIZE first
- Check if data is NEARLY SORTED
- Consider MEMORY constraints
- Think about STABILITY requirements
- Most languages use HYBRID algorithms (Timsort)
A real-world analogy
Choosing the right tool for the job! A hammer is perfect for nails but terrible for screws. Similarly, each sorting algorithm excels in specific situations.