Bubble Sort
Compares neighbors, swaps if wrong order. O(n²) average. Stable. Good for learning and nearly sorted data with the swapped optimization.
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:
[1, 3, 5, 7][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:
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 orderThe 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:
Examples: Counting Sort, Radix Sort, Bucket Sort.
They can beat O(n log n) - sometimes O(n) - but only when:
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:
| Size | Approach |
|---|---|
| Small (< 20 elements) | Simple algorithms (Insertion Sort) |
| Large | Efficient algorithms (Merge Sort, Quick Sort) |
| Data State | Effect on Algorithms |
|---|---|
| Already sorted | Insertion Sort becomes O(n) |
| Reverse sorted | Some algorithms hit their worst case |
| Random | Average-case behavior matters |
| Situation | Best Choice |
|---|---|
| Limited RAM | In-place sorting (O(1) extra space) |
| Memory available | Out-of-place sorting (easier, often stable) |
| Need | Use |
|---|---|
| Order of equal elements must be preserved | Stable sort |
| Order does not matter | Any sort |
| Data | Best Type |
|---|---|
| Small integers with known range | Non-comparison (Counting Sort) |
| Large values, strings, objects | Comparison 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 2Both 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.
| Algorithm | Stable? |
|---|---|
| Bubble Sort | Yes |
| Insertion Sort | Yes |
| Merge Sort | Yes |
| Selection Sort | No |
| Quick Sort | No (default) |
| Heap Sort | No |
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 separatelyPros: Easier to write, easier to make stable. Cons: Uses more memory.
| Condition | Choice |
|---|---|
| Memory is tight | In-place |
| Stability is required | Out-of-place (usually easier) |
| Large data, memory available | Either, 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.
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.

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 -> donedef 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 breakThe 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.
| Case | Time | Why |
|---|---|---|
| Best | O(n) | Already sorted - no swaps, one pass |
| Average | O(n²) | Most elements need moving |
| Worst | O(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.

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]| Case | Time | Why |
|---|---|---|
| Best | O(n²) | Always scans remaining elements |
| Average | O(n²) | Same |
| Worst | O(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.

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| Case | Time | Why |
|---|---|---|
| Best | O(n) | Already sorted - no shifting needed |
| Average | O(n²) | Elements need shifting |
| Worst | O(n²) | Reverse sorted - max shifting |
Space: O(1) - in-place. Stable: Yes. Adaptive: Yes - fastest of the three on nearly sorted data.
| Algorithm | Best | Average | Worst | Space | Stable | Adaptive |
|---|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Yes | Yes |
| Selection Sort | O(n²) | O(n²) | O(n²) | O(1) | No | No |
| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Yes | Yes |
| Situation | Best Choice |
|---|---|
| Learning / teaching | Bubble Sort |
| Swaps are expensive | Selection Sort |
| Data is nearly sorted | Insertion Sort |
| Need a stable sort | Bubble or Insertion |
| Any real production code | Merge Sort or Quick Sort (next topic) |
Never answer sorting algorithm questions directly. Use this structure:
Ask about input size - small or large?
Ask about memory - do we have extra space available?
Ask about stability - do equal elements need to keep their order?
Ask about data distribution - is it random, nearly sorted, or reverse sorted?
Ask about data range - are values integers in a known range?
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.
sort() - it uses TimSort