Back to Sorting & Searching
Q12Question 12 of 22

Implement Insertion Sort

Key Concepts
  • Insertion sort grows a sorted prefix by inserting each new element into its correct place, shifting larger ones right.
  • It is adaptive: O(n) on nearly sorted data because few shifts are needed when elements are close to their final positions.
  • Time is O(n^2) in the worst case (reverse sorted); space is O(1); the algorithm is stable.
  • Stability comes from using strictly-greater-than when shifting, so equal elements are never moved past each other.
  • It is the practical winner among simple sorts for small arrays due to tiny constant factors and no recursion.
  • TimSort and introsort use insertion sort as the base case for subarrays below a threshold of about 10-20 elements.
  • It models the natural process of inserting new items into an already-sorted sequence one at a time.

Definition

Insertion sort grows a sorted prefix by inserting each new element into its correct place, shifting bigger ones aside.
  • Insertion sort – builds the sorted array one element at a time by taking the next unsorted element and inserting it into its correct position.
  • Shifting – larger elements in the sorted prefix are shifted one step right to make room for the new element.
  • Adaptive – runs in O(n) on nearly sorted data because few shifts are needed.
  • Stable – the strictly-greater-than comparison during shifting preserves the order of equal elements.
  • Base case – used inside TimSort and introsort for subarrays below a threshold of about 10-20 elements.

Imagine a clerk at the National Bank of India sorting a hand of balance cards the way you sort playing cards. She holds a sorted group in her left hand and picks up the next card with her right. She slides that card leftward past every card bigger than it until it sits in the right spot, then picks up the next one. The left hand is always sorted; each new card is simply inserted where it belongs.

Understanding the Concepts

What is insertion sort?

Insertion sort treats the first element as a trivially sorted prefix of length one, then extends the prefix one element at a time. For each new element (the "key"), it walks backward through the sorted prefix, shifting every element larger than the key one position to the right, and then drops the key into the gap.

JAVA
package com.bank.sort;

import java.math.BigDecimal;
import java.util.Arrays;

public class InsertionSort {

    public static void insertionSort(BigDecimal[] arr) {
        for (int i = 1; i < arr.length; i++) {
            BigDecimal key = arr[i];
            int j = i - 1;
            while (j >= 0 && arr[j].compareTo(key) > 0) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    public static void main(String[] args) {
        BigDecimal[] balances = {
            new BigDecimal("50000"),
            new BigDecimal("20000"),
            new BigDecimal("80000"),
            new BigDecimal("10000"),
            new BigDecimal("40000")
        };
        insertionSort(balances);
        System.out.println("Sorted: " + Arrays.toString(balances));
    }
}
TEXT
Sorted: [10000, 20000, 40000, 50000, 80000]

Why does insertion sort excel on nearly sorted data?

If an element is already in or near its correct position, the inner while loop does zero or very few shifts and exits quickly. On fully sorted input, every key stops instantly, giving O(n) total time. This adaptivity is its signature strength.

JAVA
package com.bank.sort;

public class AdaptivePerformance {
    public static void main(String[] args) {
        // Nearly sorted: only one element out of place
        int[] nearly = {10, 20, 30, 50, 40, 60, 70, 80};
        int shifts = 0;
        for (int i = 1; i < nearly.length; i++) {
            int key = nearly[i];
            int j = i - 1;
            while (j >= 0 && nearly[j] > key) {
                nearly[j + 1] = nearly[j];
                j--;
                shifts++;
            }
            nearly[j + 1] = key;
        }
        System.out.println("Shifts on nearly sorted: " + shifts);
        System.out.println("Sorted: " + java.util.Arrays.toString(nearly));
    }
}
TEXT
Shifts on nearly sorted: 1
Sorted: [10, 20, 30, 40, 50, 60, 70, 80]

How does shifting work step by step?

The key is saved, then elements larger than the key are shifted right one by one until the correct gap is found.

JAVA
package com.bank.sort;

public class InsertionTrace {
    public static void main(String[] args) {
        int[] arr = {50, 30, 40, 10, 20};
        for (int i = 1; i < arr.length; i++) {
            int key = arr[i];
            int j = i - 1;
            System.out.print("Insert " + key + ": ");
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
            System.out.println(java.util.Arrays.toString(arr));
        }
    }
}
TEXT
Insert 30: [30, 50, 40, 10, 20]
Insert 40: [30, 40, 50, 10, 20]
Insert 10: [10, 30, 40, 50, 20]
Insert 20: [10, 20, 30, 40, 50]

Why is insertion sort stable?

Stability comes from the comparison arr[j] > key using strictly greater-than. Equal elements are never shifted past each other, so their original relative order is preserved. Using >= would still sort correctly but would break stability.

JAVA
package com.bank.sort;

public class InsertionStable {
    public static void main(String[] args) {
        int[] vals = {50, 30, 50, 20};
        String[] tags = {"50a", "30", "50b", "20"};

        for (int i = 1; i < vals.length; i++) {
            int kv = vals[i]; String kt = tags[i];
            int j = i - 1;
            while (j >= 0 && vals[j] > kv) {  // strictly greater: stable
                vals[j+1] = vals[j]; tags[j+1] = tags[j]; j--;
            }
            vals[j+1] = kv; tags[j+1] = kt;
        }
        System.out.println(java.util.Arrays.toString(tags));
        System.out.println("50a still before 50b: stable!");
    }
}
TEXT
[20, 30, 50a, 50b]
50a still before 50b: stable!

How is insertion sort different from selection sort?

| Feature | Insertion Sort | Selection Sort |
|---|---|---|
| Mechanism | Insert key into sorted prefix | Find min in unsorted, swap to front |
| Adaptive | Yes (O(n) on sorted data) | No (always scans fully) |
| Stable | Yes | No |
| Swaps | O(n^2) shifts worst case | O(n) swaps |
| Best on | Nearly sorted, small arrays | Write-minimising scenarios |

What is the time and space complexity?

| Case | Time | Reason |
|---|---|---|
| Best | O(n) | Already sorted, zero shifts |
| Average | O(n^2) | Random order |
| Worst | O(n^2) | Reverse sorted, max shifts |
| Space | O(1) | In-place, only key variable |

JAVA
package com.bank.sort;

public class InsertionComplexity {
    public static void main(String[] args) {
        for (int n : new int[]{100, 1000, 5000}) {
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) arr[i] = n - i; // worst: reversed
            long start = System.nanoTime();
            for (int i = 1; i < arr.length; i++) {
                int key = arr[i]; int j = i - 1;
                while (j >= 0 && arr[j] > key) { arr[j+1] = arr[j]; j--; }
                arr[j+1] = key;
            }
            System.out.println("n=" + n + " time=" + (System.nanoTime()-start)/1_000_000 + "ms");
        }
    }
}
TEXT
n=100 time=0ms
n=1000 time=1ms
n=5000 time=8ms

Why is insertion sort the base case for TimSort?

Insertion sort is the practical winner among simple sorts for small arrays. Below a threshold of about 10-20 elements, it often beats the fancier O(n log n) sorts because it has tiny constant factors and no recursion overhead. TimSort and introsort switch to insertion sort for small subarrays.

JAVA
package com.bank.sort;

public class TimSortBaseCase {
    public static void main(String[] args) {
        // TimSort uses insertion sort for runs below ~32 elements
        System.out.println("TimSort threshold: approximately 32 elements");
        System.out.println("Below this, insertion sort beats merge sort");
        System.out.println("because of tiny constant factors and no recursion.");
    }
}
TEXT
TimSort threshold: approximately 32 elements
Below this, insertion sort beats merge sort
because of tiny constant factors and no recursion.

How is insertion sort used in a banking streaming scenario?

At the National Bank of India, new transactions arrive one at a time and must be inserted into a running sorted list. Each new arrival is close to its final position, so the insertion needs very few shifts. This is exactly the insertion sort model applied to a streaming data problem.

JAVA
package com.bank.sort;

import java.util.ArrayList;
import java.util.List;

public class StreamingInsert {
    public static void main(String[] args) {
        List<Integer> sorted = new ArrayList<>();
        int[] incoming = {50000, 20000, 80000, 35000, 60000};

        for (int amount : incoming) {
            // Find insertion point (like insertion sort's inner loop)
            int pos = sorted.size();
            while (pos > 0 && sorted.get(pos - 1) > amount) pos--;
            sorted.add(pos, amount);
            System.out.println("Insert " + amount + ": " + sorted);
        }
    }
}
TEXT
Insert 50000: [50000]
Insert 20000: [20000, 50000]
Insert 80000: [20000, 50000, 80000]
Insert 35000: [20000, 35000, 50000, 80000]
Insert 60000: [20000, 35000, 50000, 60000, 80000]

What are common mistakes?

The most common mistake is using >= instead of > in the shift comparison, which still sorts correctly but breaks stability. Another mistake is forgetting to save the key before shifting, which overwrites it.

Visual Overview

TEXT
  Insertion Sort Step-by-Step:

  Initial: [50000, 20000, 80000, 10000, 40000]
            ^sorted^

  i=1: key=20000
    Shift 50000 right: [50000, 50000, 80000, 10000, 40000]
    Insert key at 0:   [20000, 50000, 80000, 10000, 40000]
                        ^--sorted---^

  i=2: key=80000
    No shifts needed:  [20000, 50000, 80000, 10000, 40000]
                        ^-----sorted------^

  i=3: key=10000
    Shift 80000, 50000, 20000 right:
    Insert key at 0:   [10000, 20000, 50000, 80000, 40000]
                        ^----------sorted----------^

  i=4: key=40000
    Shift 80000, 50000 right:
    Insert key at 2:   [10000, 20000, 40000, 50000, 80000]
                        ^--------------sorted--------------^

Key Takeaway

Insertion sort builds the sorted array one element at a time. It takes each next element as a key and inserts it into its correct position within the sorted prefix by shifting every larger element one step right. It uses O(1) extra space and is stable because it uses strictly-greater-than when shifting.

Its signature strength is adaptivity. On nearly sorted data the inner loop does almost no work, giving O(n) time. This is why production sorts like TimSort use insertion sort as the base case for small subarrays below about 10-20 elements, where its tiny constant factors beat the O(n log n) sorts.

In a banking system at the National Bank of India, insertion sort models the natural process of maintaining a running sorted list as new transactions arrive one at a time. Each new amount is close to its final position, so insertion is fast.

The weakness is large, randomly ordered data, where the backward shifts dominate and it degrades to O(n^2). For general-purpose sorting, always use the library sort.

Remember one sentence: insertion sort slides each new element into its correct place in the sorted prefix, excelling on nearly sorted and small data because few shifts are needed.

---

Share
Use ← → arrow keys to navigate