BeeTool.Dev - Essential Tools for developers

Stories.Tool Tabs VaultPersonal Documents
Super Debugging Context
BeeTool.Dev

Essential tools, knowledge, and playgrounds for developers.

Explore

  • Stories
  • Personal Documents
  • Features
  • Algorithms Playground

Tool Tabs

  • JSON Compare
  • SQL Formatter
  • Text Diff
  • All tools →

Learn

  • Sorting algorithms
  • Searching algorithms
  • Algorithm Race Mode
© 2024 – 2026 BeeTool.DevPrivacy policy
← Algorithms Playground
Sorting · Intermediate

Shell Sort

Insertion sort with a running start: sort far-apart elements first, then shrink the gap until one pass finishes the job.

AverageO(n^1.25) – O(n^1.5)StableNoIn-placeYesComparisonYes
Must remember

Gapped insertion sort with shrinking gaps; the last pass is plain insertion sort on nearly-sorted data.

Best O(n log n) · Avg ~O(n^1.3) (gap-dependent) · Worst O(n²) · Space O(1)

Hands on Interactive visualization

Size 40
Values
UnsortedComparingSwap / writeSortedPivot
0/0Ready — press play
Speed
Step 0 / 0
Comparisons
0
Swaps
0
Reads
0
Writes
0
Elapsed
0.0s
Frames
0
Depth
0 / 0
Aux memory
0 B

Implementation Code

export function shellSort(a: number[]): number[] {
  const n = a.length;
  // Shell's n/2 gaps for clarity; use Ciura's sequence
  // (1, 4, 10, 23, 57, 132, ...) for real workloads.
  for (let gap = n >> 1; gap > 0; gap >>= 1) {
    // Gapped insertion sort: each stride-gap slice becomes sorted.
    for (let i = gap; i < n; i++) {
      const key = a[i];
      let j = i;
      while (j >= gap && a[j - gap] > key) {
        a[j] = a[j - gap]; // shift within the gapped subsequence
        j -= gap;
      }
      a[j] = key;
    }
  }
  return a;
}

Big-O Complexity

Best
O(n log n)
Average
O(n^1.25) – O(n^1.5)
Worst
O(n²)
Space
O(1)

Complexity depends entirely on the gap sequence: Shell's n/2 gaps give O(n²) worst case; better sequences (Ciura, Sedgewick) push it toward O(n^4/3) — exact average bounds remain open.

StableNoIn-placeYesComparisonYesRecursiveNoAdaptiveYesOnlineNo

In production When to use

Embedded

The classic choice when you need better-than-quadratic speed with zero recursion and zero allocation — uClibc's qsort has famously been a shell sort, and it fits in a few dozen bytes of code.

Systems / kernels

Small, dependency-free, and stack-safe: attractive inside kernels and bootloaders where recursion depth and heap use are forbidden or tightly budgeted.

Mid-size arrays

For arrays of a few hundred to a few thousand elements, a Ciura-gap shell sort is often within striking distance of quicksort with a fraction of the code.

Nearly-sorted data

Adaptive like insertion sort, but the early long-gap passes also fix elements that are far from home — the case that makes plain insertion sort quadratic.

Interview / algorithms

A rich discussion topic: it shows how a gap schedule turns a quadratic algorithm sub-quadratic, and why its exact complexity is still an open research question.

Trade-offs Strengths & weaknesses

  • Sub-quadratic in practice with good gaps — a huge step up from insertion sort for mid-size data.
  • In-place, iterative, O(1) memory: no recursion stack, no buffers.
  • Tiny code footprint; easy to embed and audit.
  • Adaptive: benefits from existing order at every gap level.
  • Long-gap passes move displaced elements most of the way home in a few swaps.
  • Not stable — gapped moves jump equal keys over each other.
  • Performance hinges on gap-sequence choice; the obvious n/2 sequence is one of the worst.
  • No tight general complexity bound — hard to give guarantees for latency budgets.
  • Loses to O(n log n) sorts as n grows large.
  • Gapped memory access is cache-unfriendly during the early passes.

Intuition Mental model

Coarse-to-fine combing

Untangling hair: start with a wide-tooth comb to remove the big knots, switch to finer combs as the hair smooths, and finish with the finest comb gliding through.

  1. A wide-tooth comb (large gap) works on strands far apart — big tangles (far-displaced elements) resolve in a few strokes.
  2. Each finer comb (smaller gap) meets hair that is already mostly smooth, so strokes stay cheap.
  3. Crucially, finer combing never re-tangles what the wide comb fixed (h-sorted arrays stay h-sorted).
  4. The finest comb (gap 1, plain insertion sort) finds almost nothing left to fix and glides through in near-linear time.
  5. Choosing the comb widths — the gap sequence — is the whole art; bad widths leave hidden knots for the last comb.

Prep Interview questions

Why does sorting with large gaps first make the final insertion-sort pass cheap?
Each gapped pass drastically cuts the number of inversions, and a key theorem says an array that is h-sorted stays h-sorted after being k-sorted. By gap 1, elements are all near their final slots, and insertion sort is linear-ish in the few remaining inversions.
Why is Shell's original n/2, n/4, …, 1 sequence poor?
All gaps are even until the last, so odd and even positions never interact until gap 1 — worst case O(n²). Sequences with co-prime-ish gaps (Knuth's 3k+1, Ciura's empirical 1, 4, 10, 23, 57, 132…) mix the array far better.
Is shell sort stable? Why or why not?
No — a pass with gap h compares elements h apart and can move an element past an equal one sitting between them, which gap-1 insertion sort can never do.
When would you pick shell sort over quicksort or heap sort today?
Constrained environments: no recursion, no allocation, tiny code size, moderate n. It trades asymptotic guarantees for simplicity — the embedded-systems trade.
What is known about shell sort's true time complexity?
It is still open for general sequences. Known results: O(n^1.5) for Knuth's gaps, O(n^4/3) for Sedgewick's; Ciura's sequence is empirical with no proven bound. A rare example of a simple algorithm with unresolved analysis.

Watch out Common bugs

Gap sequence ending at 0 or skipping 1

The loop must run a final pass with gap exactly 1 (plain insertion sort) or the array ends almost-but-not-sorted. Integer division schedules like gap = gap / 2.2 need a floor to 1 guard.

Inner loop comparing a[j - 1] instead of a[j - gap]

Copy-pasting insertion sort and forgetting to replace every 1 with gap — indices, shifts, and the loop condition all need the gap. One missed spot corrupts the pass.

Unsigned underflow in j >= gap checks

In unsigned-index languages, writing j - gap >= 0 is always true and wraps around. Compare j >= gap before touching a[j - gap].

Assuming stability downstream

Swapping a stable sort for shell sort in a pipeline that multi-key sorts by chaining passes silently breaks the secondary ordering — the gapped moves reorder equal keys.

One screen Cheat sheet

Gapped insertion sort with shrinking gaps; the last pass is plain insertion sort on nearly-sorted data.

Best O(n log n) · Avg ~O(n^1.3) (gap-dependent) · Worst O(n²) · Space O(1)
  • Complexity is a function of the gap sequence — Ciura (1, 4, 10, 23, 57, …) is the practical pick.
  • In-place, iterative, allocation-free: the embedded-systems sweet spot.
  • Not stable, unlike its gap-1 special case insertion sort.
  • h-sorted stays h-sorted through later passes — that is why gaps compose.
  • Must finish with gap = 1 or the result is not sorted.
  • Exact average-case analysis is still an open problem.
Pick whenModerate n with hard constraints: no recursion, no allocation, minimal code — and stability not required.
Avoid whenLarge datasets needing guaranteed O(n log n), or any pipeline that depends on stability.

Heap Sort

Counting Sort