cv.cm

Coding · Algorithms

Insertion sort explained: how it works, code and complexity

5 min read

Short answer

Insertion sort builds the sorted result one element at a time. It takes the next element, shifts every larger element in the sorted part one position to the right, and drops the element into the gap, the way many people sort playing cards in their hand. It is O(n²) in the average and worst case, O(n) on input that is already or nearly sorted, uses O(1) extra space and is stable. It is the fastest simple sort for small or nearly sorted arrays, which is why hybrid sorts such as Timsort use it.

Open Sort lines

The idea in plain words

Treat the first element as a sorted list of length one. Take the second element and insert it before or after the first. Take the third element and insert it into the right place among the first two. Continue until every element has been inserted.

To insert, hold the new element aside (call it key), then walk left through the sorted part. Every element larger than key moves one position right. When you reach an element that is not larger, or the start of the list, put key in the empty slot.

Worked example, step by step

Sort [12, 11, 13, 5, 6]. The part before the bar is sorted (but not yet final).

StepkeyShiftsList after the step
11112 moves right[11, 12 | 13, 5, 6]
213none, 12 is smaller[11, 12, 13 | 5, 6]
3513, 12, 11 move right[5, 11, 12, 13 | 6]
4613, 12, 11 move right[5, 6, 11, 12, 13]

Notice step 2: an element that is already in place costs a single comparison. That is why insertion sort is fast on nearly sorted input.

Python implementation

def insertion_sort(items):
    """Sort a list in place, ascending. Returns the same list."""
    for i in range(1, len(items)):
        key = items[i]
        j = i - 1
        while j >= 0 and items[j] > key:   # strict >, keeps it stable
            items[j + 1] = items[j]        # shift right
            j -= 1
        items[j + 1] = key                 # drop into the gap
    return items
print(insertion_sort([12, 11, 13, 5, 6]))   # [5, 6, 11, 12, 13]
print(insertion_sort([1, 2, 3, 4]))         # [1, 2, 3, 4], 3 comparisons

Shifting instead of swapping writes each moved element once, which is about half the work of the swap-based version. You can also find the insertion point with binary search, as below. This reduces comparisons to O(n log n) in total, but the shifts are still O(n²), so the overall time stays O(n²).

from bisect import insort_right
def binary_insertion_sort(items):
    result = []
    for x in items:
        insort_right(result, x)   # stable: equal items go after
    return result
print(binary_insertion_sort([3, 1, 2, 1]))   # [1, 1, 2, 3]

Time and space complexity

CaseTimeReason
Best (already sorted)O(n)each element needs one comparison and no shifts
Average (random order)O(n²)about n²/4 shifts on average
Worst (reverse order)O(n²)element i shifts past all i earlier elements
Extra spaceO(1)sorts in place with one temporary value

More precisely, the running time is O(n + I), where I is the number of inversions (pairs out of order). If every element is at most k positions from its final place, I is at most n · k, so insertion sort is O(n · k): very fast when k is small.

How insertion sort compares with other sorts

AlgorithmBestAverageWorstExtra spaceStable
Insertion sortO(n)O(n²)O(n²)O(1)Yes
Bubble sortO(n)O(n²)O(n²)O(1)Yes
Selection sortO(n²)O(n²)O(n²)O(1)No
Merge sortO(n log n)O(n log n)O(n log n)O(n)Yes
QuicksortO(n log n)O(n log n)O(n²)O(log n)No
HeapsortO(n log n)O(n log n)O(n log n)O(1)No
Timsort (Python sorted)O(n)O(n log n)O(n log n)O(n)Yes

Insertion sort is stable because an element only moves left past elements that are strictly greater. It is also online: it can sort data as it arrives, keeping a sorted prefix at every moment. For short lists, its low overhead beats O(n log n) algorithms, so Timsort (used by Python and by Java for objects) sorts short runs with binary insertion sort, and many quicksort implementations switch to insertion sort below a small size.

Variants and common mistakes

  • Binary insertion sort: binary search for the position, then shift. Fewer comparisons, which helps when comparing is expensive.
  • Shell sort: run insertion sort on elements a gap apart, then shrink the gap down to 1. It moves far-away elements quickly and is much faster than plain insertion sort on large arrays.
  • Linked-list insertion sort: no shifting is needed, but finding the position is still a linear scan.

When to use insertion sort

Use it for small arrays (tens of elements), for data that is nearly sorted, such as a list that was sorted and then had a few items appended, and when you need a simple stable in-place sort. For large random data, use an O(n log n) sort; in Python that means sorted() or list.sort(), which already apply insertion sort to small runs internally.

Interview questions often ask for the trace, the best and worst cases, why it is stable, or why it beats O(n log n) algorithms on small and nearly sorted input.

FAQ

Is insertion sort stable?

Yes. Elements move left only past elements that are strictly greater, so equal elements stay in their original order.

What is the best case of insertion sort?

O(n), when the input is already sorted: each new element is compared once with its left neighbour and stays in place.

Why is insertion sort used in Timsort?

For short sequences, its very low overhead makes it faster than merge sort or quicksort, and it is stable. Timsort uses binary insertion sort to extend short runs before merging them.

Is insertion sort better than bubble sort?

Yes, in practice. Both are O(n²) and stable, but insertion sort performs fewer comparisons and writes and adapts better to nearly sorted input.

Does binary search make insertion sort O(n log n)?

No. It reduces the number of comparisons to O(n log n), but shifting elements to make room is still O(n²) in the worst case.

All coding tutorials