Last Updated: March 31, 2026
Shell Sort is an optimization of Insertion Sort designed to handle larger inputs more efficiently. Instead of comparing adjacent elements right away, it starts by comparing elements that are far apart, gradually reducing the gap between them. This allows the algorithm to move elements closer to their correct positions early on.
By the time the gap becomes 1, the array is already partially sorted, making the final pass much faster than a regular Insertion Sort. This simple idea significantly improves performance in practice.
In this chapter, you will learn how Shell Sort works, how the gap sequence affects its performance, and how to implement it. You will also understand why it performs better than basic quadratic sorting algorithms in many real-world scenarios.
Shell sort is a generalization of insertion sort that allows the exchange of elements that are far apart. Instead of comparing adjacent elements, it compares elements separated by a "gap" and uses insertion sort on each group of elements spaced by that gap. Over successive passes, the gap shrinks until it reaches 1, at which point the algorithm performs a standard insertion sort on an array that is already nearly sorted.
The key idea is this: by sorting elements that are far apart early on, we eliminate large amounts of disorder in the array quickly. Each pass with a smaller gap brings the array closer to sorted order. By the time the gap reaches 1, most elements are already close to their final positions, so the last pass runs in nearly O(n) time.
Here is how Shell sort compares elements at different gap values versus regular insertion sort:
In insertion sort, each element can only compare with its immediate neighbor. Shell sort breaks this restriction by comparing elements separated by a gap. Elements in the same gap-group get sorted together, so large displacements are resolved early.
Why this matters: Insertion sort runs in O(n^2) because elements can only move one position per comparison. If an element needs to travel n positions, it takes n swaps. Shell sort lets elements jump across large distances in a single step, dramatically reducing the total number of operations.
The algorithm follows a simple three-level loop structure:
When the gap is larger than 1, the algorithm groups elements that are gap positions apart and sorts each group using insertion sort. As the gap decreases, the groups overlap more and more. By the time the gap reaches 1, we are doing a standard insertion sort, but on an array that is already nearly sorted.
The choice of gap sequence has a significant impact on performance. Different sequences yield different time complexities:
| Gap Sequence | Formula | Example (n=16) | Worst-Case Time |
|---|---|---|---|
| Shell's original | n/2, n/4, ..., 1 | 8, 4, 2, 1 | O(n^2) |
| Knuth's | (3^k - 1) / 2 | 1, 4, 13, 40, 121, ... | O(n^(3/2)) |
| Hibbard's | 2^k - 1 | 1, 3, 7, 15, 31, ... | O(n^(3/2)) |
| Sedgewick's | 4^k + 3*2^(k-1) + 1 | 1, 5, 19, 41, 109, ... | O(n^(4/3)) |
| Tokuda's | ceil((9*(9/4)^k - 4) / 5) | 1, 4, 9, 20, 46, ... | Unknown (empirically fast) |
Shell's original sequence (n/2) is the simplest and most commonly taught, but it is not the most efficient. Knuth's and Sedgewick's sequences perform better in practice because they avoid the issue of "increment interaction," where certain gap values fail to compare elements that were already compared in previous passes.
For interviews and most practical purposes, Shell's original sequence (dividing by 2 each time) is perfectly acceptable. Just be aware that better sequences exist.
The outer loop controls the gap, the middle loop iterates through elements starting from index gap, and the inner loop performs the gapped insertion sort. This inner loop is identical to insertion sort, except instead of comparing with the previous element (j-1), it compares with the element gap positions back (j-gap).
When the gap finally reaches 1, the algorithm performs one last pass of standard insertion sort. But because the earlier passes have already moved elements close to their final positions, this last pass does very little work, typically running in close to O(n) time.
The algorithm itself is short, with no recursion and no auxiliary data structures. This simplicity is one of Shell sort's biggest advantages.
Let us trace through Shell sort on the array [12, 34, 54, 2, 3] using Shell's original gap sequence (n/2).
Initial array: [12, 34, 54, 2, 3], n = 5
With gap = 2, we are sorting elements that are 2 positions apart. This creates the following interleaved sub-arrays:
[12, 54, 3][34, 2]Now we walk through each element starting from index gap = 2:
i = 2: temp = 54, j = 2
[12, 34, 54, 2, 3]i = 3: temp = 2, j = 3
[12, 2, 54, 34, 3]i = 4: temp = 3, j = 4
[3, 2, 12, 34, 54]After gap = 2, the sub-arrays are sorted:
[3, 12, 54] (was [12, 54, 3])[2, 34] (was [34, 2])The array [3, 2, 12, 34, 54] is already much more ordered than the original. Large elements moved to the right, small elements moved to the left.
This is standard insertion sort, but on a nearly sorted array.
i = 1: temp = 2, j = 1
[2, 3, 12, 34, 54]i = 2: temp = 12, j = 2
[2, 3, 12, 34, 54]i = 3: temp = 34, j = 3
[2, 3, 12, 34, 54]i = 4: temp = 54, j = 4
[2, 3, 12, 34, 54]Final sorted array: [2, 3, 12, 34, 54]
Notice how the gap = 1 pass only needed one swap (moving 2 before 3). The heavy lifting was done in the gap = 2 pass. This is exactly why Shell sort is faster than plain insertion sort: the final pass operates on an almost-sorted array.
Shell sort's complexity is unique among sorting algorithms because it depends heavily on the gap sequence, not just the input.
| Gap Sequence | Worst Case | Average Case | Best Case |
|---|---|---|---|
| Shell's (n/2) | O(n^2) | O(n^(3/2)) | O(n log n) |
| Knuth's (3h+1) | O(n^(3/2)) | O(n^(7/6)) | O(n log n) |
| Hibbard's (2^k - 1) | O(n^(3/2)) | O(n^(5/4)) | O(n log n) |
| Sedgewick's | O(n^(4/3)) | O(n^(7/6)) | O(n log n) |
Best case (O(n log n)): When the array is already sorted, each gap pass makes one comparison per element without any shifts. Since there are O(log n) gap values, the total is O(n log n).
Worst case with Shell's sequence (O(n^2)): This happens when the gap sequence causes redundant comparisons. For example, with gaps 8, 4, 2, 1, elements at odd indices are never compared with elements at even indices until the very last pass, which then degenerates into a slow insertion sort.
Why better sequences help: Knuth's and Sedgewick's sequences are designed so that consecutive gap values share fewer common factors. This ensures that each pass compares elements that previous passes missed, leading to better performance.
Shell sort uses O(1) auxiliary space. It sorts in-place, using only a single temporary variable for the insertion sort swap. This makes it one of the most memory-efficient sorting algorithms.
Shell sort is not stable. Equal elements may change their relative order during gapped passes. Consider two equal elements at positions 0 and 3 with gap = 2. They belong to different sub-arrays and might be rearranged relative to each other.
Here is a summary comparing Shell sort with related algorithms:
| Property | Insertion Sort | Shell Sort | Merge Sort | Quick Sort |
|---|---|---|---|---|
| Best Case | O(n) | O(n log n) | O(n log n) | O(n log n) |
| Average Case | O(n^2) | O(n^(3/2))* | O(n log n) | O(n log n) |
| Worst Case | O(n^2) | O(n^(3/2))* | O(n log n) | O(n^2) |
| Space | O(1) | O(1) | O(n) | O(log n) |
| Stable | Yes | No | Yes | No |
| In-Place | Yes | Yes | No | Yes |
| Recursive | No | No | Yes | Yes |
*With Knuth's gap sequence. Shell's original sequence gives O(n^2) worst case.