Part 3 · Lesson 16 · 22 min read

Selection, Bubble and Insertion Sort

Three simple sorts that show what sorting really does.

On this page

  1. What sorting means
  2. Stability: keeping ties in order
  3. Selection sort
  4. Bubble sort and the early exit
  5. Insertion sort
  6. Traced: insertion sort on [5, 2, 4, 1]
  7. Why insertion sort loves nearly sorted data
  8. The three side by side
  9. Practice questions (6)
  10. Make it stick
  11. What's next

What sorting means

Sorting means putting items in order: numbers from smallest to largest, words in alphabet order, or people by age. It is one of the most useful things you can do to data. Once an array is sorted, equal values sit next to each other, and the smallest and largest values are at the two ends. You can also search a sorted array by halving it again and again (Lesson 28).

In JavaScript you will almost always sort with arr.sort((a, b) => a - b). (Lesson 18 explains why the comparison function (a, b) => a - b is needed.) So why learn to sort by hand? The three simple sorts in this lesson teach three core moves: find the smallest item, swap neighbours, and insert an item into a sorted part. These moves appear inside many interview problems. They also make the O(n log n) sorts in the next two lessons easier to understand. (O(n²) means the work grows with n × n. O(n log n) is much smaller, so it is much faster.)

Stability: keeping ties in order

When two items compare as equal, a stable sort keeps them in the order that they had before sorting. This matters when you sort records (objects with several fields) by one field:

JavaScript
const students = [  { name: "Asha",  grade: "B" },  { name: "Ben",   grade: "A" },  { name: "Chen",  grade: "B" },  { name: "Dara",  grade: "A" },];students.sort((a, b) => a.grade.localeCompare(b.grade));console.log(students.map((s) => s.name).join(", "));// Ben, Dara, Asha, Chen   — A's first; within each grade, the original order is kept

Asha came before Chen and both have a B, so a stable sort keeps Asha first. JavaScript's sort has been guaranteed stable since 2019 (ES2019). There is one more property to know. An in-place sort rearranges the array itself and uses only O(1) extra memory (a fixed small amount that does not grow with n).

Selection sort

Selection sort is a sorting algorithm that repeatedly selects the smallest remaining item and puts it in its place. Idea: find the smallest item and put it first. Then find the smallest of the rest and put it second. Repeat until the array is sorted. (To swap two items means to exchange their positions.)

JavaScript
function selectionSort(arr) {  const n = arr.length;  for (let i = 0; i < n - 1; i++) {    let minIdx = i;    for (let j = i + 1; j < n; j++) {           // find the smallest in arr[i..]      if (arr[j] < arr[minIdx]) minIdx = j;    }    [arr[i], arr[minIdx]] = [arr[minIdx], arr[i]];  // put it at position i  }  return arr;}console.log(selectionSort([5, 3, 8, 1, 4])); // [ 1, 3, 4, 5, 8 ]
Dry run · selectionSort([5, 3, 8, 1, 4])
iSmallest in arr[i..]SwapArray after
01 (index 3)5 ↔ 1[1, 3, 8, 5, 4]
13 (index 1)none needed[1, 3, 8, 5, 4]
24 (index 4)8 ↔ 4[1, 3, 4, 5, 8]
35 (index 3)none needed[1, 3, 4, 5, 8]

After pass i, positions 0..i hold the i + 1 smallest values in their final places.

The inner loop always scans the whole unsorted part. That is (n − 1) + (n − 2) + … + 1 ≈ n²/2 comparisons, even if the array is already sorted. So selection sort is O(n²) in every case. Its one advantage is that it makes at most n − 1 swaps. It is not stable. A swap over a long distance can move an item past an equal item.

Bubble sort and the early exit

Bubble sort is a sorting algorithm that repeatedly compares neighbours and swaps them when they are in the wrong order. Idea: walk through the array, compare each pair of neighbours, and swap any pair that is in the wrong order. After one full pass (one walk through the array), the largest value has “bubbled” to the end, like an air bubble rising in water. Repeat on the rest.

JavaScript
function bubbleSort(arr) {  const n = arr.length;  for (let pass = 0; pass < n - 1; pass++) {    let swapped = false;    for (let j = 0; j < n - 1 - pass; j++) {     // the last "pass" items are already in place      if (arr[j] > arr[j + 1]) {        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];        swapped = true;      }    }    if (!swapped) break;                         // no swaps: already sorted, stop early  }  return arr;}console.log(bubbleSort([5, 3, 8, 1, 4])); // [ 1, 3, 4, 5, 8 ]console.log(bubbleSort([1, 2, 3, 4, 5])); // [ 1, 2, 3, 4, 5 ]   one pass, then stop
Dry run · bubbleSort([5, 3, 8, 1, 4]) — one row per pass
PassSwaps madeArray afterFixed at the end
05↔3, 8↔1, 8↔4[3, 5, 1, 4, 8]8
15↔1, 5↔4[3, 1, 4, 5, 8]5, 8
23↔1[1, 3, 4, 5, 8]4, 5, 8
3none → stop early[1, 3, 4, 5, 8]all

In the worst case it is O(n²), like selection sort. But the swapped flag gives it an early exit. If a whole pass makes no swaps, the array is already sorted, so we stop. On an array that is already sorted, that is just one pass, so it takes O(n). Bubble sort is stable, because it swaps only neighbours that are strictly out of order (equal neighbours stay where they are).

Insertion sort

Insertion sort is a sorting algorithm that builds a sorted part one item at a time, by inserting each new item into its correct place. Idea: this is how most people sort playing cards in their hand. Keep a sorted part on the left. Take the next item (called the key) and slide it left past every bigger item until it reaches its place.

Traced: insertion sort on [5, 2, 4, 1]

step 1 / 29
Set up
arr = [5, 2, 4, 1]

The first item alone counts as a sorted part of length 1.

▶const arr = [5, 2, 4, 1];2for (let i = 1; i < arr.length; i++) {3  const key = arr[i];4  let j = i - 1;5  while (j >= 0 && arr[j] > key) {6    arr[j + 1] = arr[j];7    j--;8  }9  arr[j + 1] = key;10}11console.log(arr);
Variables
arr[5, 2, 4, 1]
Console
(nothing printed yet)
Each new key is compared with the sorted part from right to left. Bigger items shift one place right; the key drops into the gap. · interactive
arr =
2
0
4
1
5
2
key
1
3
↑ index (position), always starting at 0
Before the last pass: the first three items are sorted. Key 1 will shift 5, 4 and 2 to the right and land at index 0.

Why insertion sort loves nearly sorted data

The while loop stops as soon as it finds an item that is not bigger than the key. If the array is already sorted, it stops at once every time. That is n − 1 comparisons in total, so O(n). If every item is only a few places from where it belongs, each key moves only a few places, so the total is still close to O(n). Only a reversed array forces every key all the way to the front, and that costs O(n²).

Insertion sort in real life

Insertion sort is very fast on small or nearly sorted arrays, so real sorting functions use it inside. V8 is the JavaScript engine in Chrome and Node.js. Its sort uses an algorithm called TimSort. TimSort sorts small sections with insertion sort and then merges them. The merging step is Lesson 17.

The three side by side

SelectionBubble (early exit)Insertion
Best case (already sorted)O(n²)O(n)O(n)
Worst case (reversed)O(n²)O(n²)O(n²)
Extra spaceO(1)O(1)O(1)
Stable?NoYesYes
Swaps / writesAt most n − 1 swapsMany swapsMany shifts
Best used forWhen writes are expensiveTeaching onlySmall or nearly sorted arrays

In the worst case all three are O(n²). So none of them can sort 100,000 items in time (Lesson 12). For that you need the O(n log n) sorts: merge sort and quick sort.

Practice questions

These questions use the moves of the simple sorts. They do not ask you to sort a whole array. Say the time complexity of each answer.

Q1Insert into a sorted arrayEasy

Insert x into the sorted array arr so that it stays sorted.

Example 1
Input: arr = [1, 3, 5, 8], x = 4
Output: [1, 3, 4, 5, 8]
Explanation: 4 belongs between 3 and 5.
Example 2
Input: arr = [2, 6], x = 9
Output: [2, 6, 9]
Explanation: Edge case: x is the largest, so it goes at the end.
Example 3
Input: arr = [], x = 7
Output: [7]
Explanation: Edge case: empty array.
Stuck? Open one hint at a time
Hint 1
This is exactly one step of insertion sort: x is the key.
Hint 2
Push x onto the end to make room, then shift the bigger items one place right, starting from the end.
Show all 2 solutions with explanations
Approach 1Shift from the end
The idea

Make the array one item longer. Then walk from the end and move every item that is bigger than x one place right. Put x in the gap.

JavaScript
function insertSorted(arr, x) {  let j = arr.length - 1;  arr.push(x);                         // make room (this slot is overwritten when needed)  while (j >= 0 && arr[j] > x) {    arr[j + 1] = arr[j];    j--;  }  arr[j + 1] = x;  return arr;}console.log(insertSorted([1, 3, 5, 8], 4)); // [ 1, 3, 4, 5, 8 ]console.log(insertSorted([2, 6], 9));       // [ 2, 6, 9 ]console.log(insertSorted([], 7));           // [ 7 ]
How it works

O(n) in the worst case (x is smaller than everything). It is O(1) when x is the largest. It is in-place, which means it makes no second array.

Approach 2Find the position, then splice
The idea

Find the first index whose value is bigger than x. Then insert x there with splice (an array method that can add or remove items at any position).

JavaScript
function insertSorted(arr, x) {  let i = 0;  while (i < arr.length && arr[i] <= x) i++;  arr.splice(i, 0, x);  return arr;}console.log(insertSorted([1, 3, 5, 8], 4)); // [ 1, 3, 4, 5, 8 ]
How it works

This is also O(n). The search is O(n), and splice shifts the later items (a loop that you cannot see, Lesson 12). Lesson 28 finds the position in O(log n), but the shifting still costs O(n).

Which approach should you use?

Approach 1 is the inner loop of insertion sort. If you understand it, the full sort is easy to write from memory.

Q2Height checkerEasy

Students should stand in non-decreasing order of height (each student is as tall as or taller than the one before). Return how many positions do not match the sorted order.

Example 1
Input: [1, 1, 4, 2, 1, 3]
Output: 3
Explanation: Sorted is [1, 1, 1, 2, 3, 4]. Positions 2, 4 and 5 differ.
Example 2
Input: [5, 1, 2, 3, 4]
Output: 5
Explanation: Sorted is [1, 2, 3, 4, 5]: every position differs.
Example 3
Input: [1, 2, 3]
Output: 0
Explanation: Already in order.
Stuck? Open one hint at a time
Hint 1
Make a sorted copy. Then compare the two arrays position by position.
Hint 2
Heights are between 1 and 100. A counting array (Lesson 15) can give you the sorted order without comparing items.
Show all 2 solutions with explanations
Approach 1Sort a copy, compare
The idea

Copy the array, sort the copy with a comparator (the function (a, b) => a - b), and count the differences.

JavaScript
function heightChecker(heights) {  const expected = [...heights].sort((a, b) => a - b);  let count = 0;  for (let i = 0; i < heights.length; i++) {    if (heights[i] !== expected[i]) count++;  }  return count;}console.log(heightChecker([1, 1, 4, 2, 1, 3])); // 3console.log(heightChecker([5, 1, 2, 3, 4]));    // 5
How it works

O(n log n) for the sort. Copying with [...heights] is important, because sort changes the array that it is called on.

Approach 2Counting sort
The idea

Count each height. Then walk the heights from 1 upwards. This walk gives the sorted order, and we compare it with the original as we go.

JavaScript
function heightChecker(heights) {  const count = new Array(101).fill(0);  for (const h of heights) count[h]++;  let mismatches = 0;  let h = 1;  for (const actual of heights) {    while (count[h] === 0) h++;        // next height in sorted order    if (actual !== h) mismatches++;    count[h]--;  }  return mismatches;}console.log(heightChecker([1, 1, 4, 2, 1, 3])); // 3console.log(heightChecker([1, 2, 3]));          // 0
How it works

O(n + 100) = O(n). This is counting sort, a sorting method that counts how many times each value appears and then writes the values out in order. When the values come from a small range, you can sort without comparing items at all.

Which approach should you use?

Approach 1 is the normal answer. Mention counting sort when the range of values is small. It beats the O(n log n) limit of sorts that compare items. (LeetCode 1051.)

Q3The k smallest, with selection passesEasy

Return the k smallest values of nums in increasing order.

Example 1
Input: nums = [7, 2, 9, 4, 1], k = 2
Output: [1, 2]
Explanation: The two smallest values, in increasing order.
Example 2
Input: nums = [3, 3, 1], k = 3
Output: [1, 3, 3]
Explanation: k equals the length, so the answer is the whole array, sorted.
Stuck? Open one hint at a time
Hint 1
Each pass of selection sort puts the next smallest value in its final place.
Hint 2
You only need k passes, not n.
Show all 2 solutions with explanations
Approach 1k passes of selection sort
The idea

Run the outer loop of selection sort only k times. Then return the first k items.

JavaScript
function kSmallest(nums, k) {  const a = [...nums];  for (let i = 0; i < k; i++) {    let minIdx = i;    for (let j = i + 1; j < a.length; j++) {      if (a[j] < a[minIdx]) minIdx = j;    }    [a[i], a[minIdx]] = [a[minIdx], a[i]];  }  return a.slice(0, k);}console.log(kSmallest([7, 2, 9, 4, 1], 2)); // [ 1, 2 ]console.log(kSmallest([3, 3, 1], 3));       // [ 1, 3, 3 ]
How it works

O(n × k). This is good when k is tiny (say 3), and worse than sorting when k is large.

Approach 2Sort, then slice
The idea

Sort a copy and take the first k.

JavaScript
function kSmallest(nums, k) {  return [...nums].sort((a, b) => a - b).slice(0, k);}console.log(kSmallest([7, 2, 9, 4, 1], 2)); // [ 1, 2 ]
How it works

O(n log n), whatever k is.

Which approach should you use?

Compare n × k with n log n. For n = 106, log n is about 20, so selection passes win only when k < 20. Lesson 46 gives an O(n log k) answer with a heap (a structure that always gives you the smallest or largest item quickly).

Q4How many swaps does bubble sort make?Medium

Return how many swaps bubble sort makes to sort nums.

Example 1
Input: [3, 1, 2]
Output: 2
Explanation: 3↔1 gives [1, 3, 2], then 3↔2 gives [1, 2, 3].
Example 2
Input: [5, 3, 8, 1, 4]
Output: 6
Explanation: 3 swaps in the first pass, 2 in the second, 1 in the third (see the dry run in the lesson).
Example 3
Input: [1, 2, 3]
Output: 0
Explanation: Already sorted.
Stuck? Open one hint at a time
Hint 1
You could run bubble sort and count the swaps. Is there a way to get the number without sorting?
Hint 2
Each swap of neighbours fixes exactly one pair (i, j) with i < j and nums[i] > nums[j]. Such a pair, where a bigger number comes before a smaller one, is called an inversion.
Show all 2 solutions with explanations
Approach 1Simulate and count
The idea

Run bubble sort on a copy and add 1 to a counter for every swap.

JavaScript
function bubbleSwaps(nums) {  const a = [...nums];  let swaps = 0;  for (let pass = 0; pass < a.length - 1; pass++) {    for (let j = 0; j < a.length - 1 - pass; j++) {      if (a[j] > a[j + 1]) {        [a[j], a[j + 1]] = [a[j + 1], a[j]];        swaps++;      }    }  }  return swaps;}console.log(bubbleSwaps([3, 1, 2]));       // 2console.log(bubbleSwaps([5, 3, 8, 1, 4])); // 6
How it works

O(n²) time.

Approach 2Count the inversions
The idea

Count every pair i < j with nums[i] > nums[j]. Each such pair needs exactly one swap of neighbours to fix, and each swap fixes exactly one pair.

JavaScript
function bubbleSwaps(nums) {  let inversions = 0;  for (let i = 0; i < nums.length; i++) {    for (let j = i + 1; j < nums.length; j++) {      if (nums[i] > nums[j]) inversions++;    }  }  return inversions;}console.log(bubbleSwaps([3, 1, 2]));       // 2console.log(bubbleSwaps([5, 3, 8, 1, 4])); // 6console.log(bubbleSwaps([1, 2, 3]));       // 0
How it works
Dry run · inversions in [5, 3, 8, 1, 4]
Bigger value firstPairs
5(5, 3), (5, 1), (5, 4)
3(3, 1)
8(8, 1), (8, 4)

6 inversions = 6 swaps.

Which approach should you use?

Both are O(n²), but Approach 2 explains why the answer is that number. Lesson 17 counts inversions in O(n log n) by changing merge sort slightly.

Q5A stable sort with a comparatorMedium

Write a stable sort that accepts a comparison function, and use it to sort students by grade.

Example 1
Input: [{Asha, B}, {Ben, A}, {Chen, B}, {Dara, A}]
Output: Ben, Dara, Asha, Chen
Explanation: Sorted by grade. Ben comes before Dara, and Asha before Chen, as in the input.
Stuck? Open one hint at a time
Hint 1
Write an insertion sort that takes a function cmp(a, b). The function returns a negative number, zero or a positive number, in the same way as the function you give to JavaScript's sort. (A negative number means a comes first, zero means they are equal, and a positive number means b comes first.)
Hint 2
To stay stable, shift an item only when it is strictly greater than the key (greater, not equal).
Show the solution with explanations
Approach 1Insertion sort with cmp
The idea

Replace arr[j] > key with cmp(arr[j], key) > 0.

JavaScript
function insertionSort(arr, cmp) {  for (let i = 1; i < arr.length; i++) {    const key = arr[i];    let j = i - 1;    while (j >= 0 && cmp(arr[j], key) > 0) {   // strictly greater: equal items never pass each other      arr[j + 1] = arr[j];      j--;    }    arr[j + 1] = key;  }  return arr;}const students = [  { name: "Asha", grade: "B" },  { name: "Ben", grade: "A" },  { name: "Chen", grade: "B" },  { name: "Dara", grade: "A" },];insertionSort(students, (a, b) => a.grade.localeCompare(b.grade));console.log(students.map((s) => s.name).join(", ")); // Ben, Dara, Asha, Chen
How it works

If the condition were >= 0, equal items would jump over each other and the sort would become unstable. Chen would end up before Asha. One character decides stability.

Which approach should you use?

Real sorting functions work with a comparator in this way, and the next two lessons do the same.

Q6Sort an array (large input)Medium

Sort nums in ascending order. Constraints: up to 5 × 104 values, each between −5 × 104 and 5 × 104.

Example 1
Input: [5, 2, 3, 1]
Output: [1, 2, 3, 5]
Explanation: Plain ascending order.
Example 2
Input: [5, 1, 1, 2, 0, 0]
Output: [0, 0, 1, 1, 2, 5]
Explanation: Duplicates stay.
Stuck? Open one hint at a time
Hint 1
Read the constraints: up to 5 × 104 values. Is O(n²) fast enough? (See Lesson 12.)
Hint 2
Values are between −5 × 104 and 5 × 104, a range of about 105. Add a fixed number to every value so that the smallest value maps to index 0.
Show all 3 solutions with explanations
Approach 1Insertion sort
The idea

The algorithm from this lesson.

JavaScript
function sortArray(nums) {  for (let i = 1; i < nums.length; i++) {    const key = nums[i];    let j = i - 1;    while (j >= 0 && nums[j] > key) {      nums[j + 1] = nums[j];      j--;    }    nums[j + 1] = key;  }  return nums;}console.log(sortArray([5, 2, 3, 1])); // [ 1, 2, 3, 5 ]
How it works

This is correct, but it is O(n²). For 5 × 104 values in reverse order, that is over a billion shifts. It fails LeetCode's time limit, which is the point of the question.

Approach 2Counting sort over the value range
The idea

Count each value at index value + 50000, then write the values back into the array in order.

JavaScript
function sortArray(nums) {  const OFFSET = 50000;  const count = new Array(100001).fill(0);  for (const x of nums) count[x + OFFSET]++;  let i = 0;  for (let v = 0; v < count.length; v++) {    while (count[v]-- > 0) nums[i++] = v - OFFSET;  }  return nums;}console.log(sortArray([5, 2, 3, 1]));       // [ 1, 2, 3, 5 ]console.log(sortArray([5, 1, 1, 2, 0, 0])); // [ 0, 0, 1, 1, 2, 5 ]console.log(sortArray([-3, 4, -50000]));    // [ -50000, -3, 4 ]
How it works

O(n + range) time and O(range) space. The offset (the fixed number we add) makes negative values fit as array indexes.

Approach 3Built-in sort
The idea

Use JavaScript's sort with a numeric comparator.

JavaScript
function sortArray(nums) {  return nums.sort((a, b) => a - b);}console.log(sortArray([5, 1, 1, 2, 0, 0])); // [ 0, 0, 1, 1, 2, 5 ]
How it works

O(n log n). In a real interview the interviewer would often forbid this. The next two lessons build merge sort and quick sort to replace it.

Which approach should you use?

What to learn here: simple sorts are O(n²) and too slow for large inputs. Counting sort is a valid O(n) answer when the range is small. Otherwise you need an O(n log n) sort. (LeetCode 912.)

Make it stick

Make it stick — close this page, then
  1. Describe selection, bubble and insertion sort in one sentence each.
  2. Write insertion sort from memory and dry-run it on [3, 1, 2].
  3. Explain what makes a sort stable, with the students example.
  4. Say which of the three is O(n) on sorted input, and why selection sort is not.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

Lesson 17 uses recursion from Lesson 14 to sort much faster. Split the array in half, sort each half, and merge the two sorted halves into one. This is merge sort, and it is O(n log n) in every case.

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series