M4RKYU.SYSEdition 2027
Skip to content
LOCEN/Ontario · CA/▸logs · js sorting algorithms every developer should know 1holStandbyOK/--:--:--EST
M4M4RK_YUportfolio
  • Build
    BuildOverview
    • WorkSelected case studies and write-ups
    • GamesPlayable prototypes and game-dev logs
  • Gallery
    GalleryOverview
    • PhotosPhoto collections and visual experiments
    • ShopPrints, posters, and one-off objects
  • Writing
    WritingOverview
    • BlogLong-form devlogs and field notes
    • NotesShort observations, links, snippets
  • Resources
    ResourcesOverview
    • Tools38 in-browser developer utilities
    • LinksDaily-use dev and design bookmarks
  • About
  • Contact
中文

syndicated · dev.to / @markyu

JS Sorting Algorithms Every Developer Should Know

You call .sort() every day without thinking about it. But the sorting algorithm your language's...

Published
Jun 16
·
Reading time
9 min read
·
Reactions
5
algorithmsbeginnerstutorialjavascript
View on dev.to

On this page

  • The two camps
  • 1. Bubble Sort - the one everyone knows, no one uses
  • 2. Selection Sort - simpler, not better
  • 3. Insertion Sort - the underrated one
  • 4. Shell Sort - insertion sort, but turbocharged
  • 5. Merge Sort - the stable workhorse
  • 6. Quick Sort - the fast one (usually)
  • 7. Heap Sort - the Linux kernel's pick
  • 8. Radix Sort - breaking the comparison barrier
  • What .sort() is actually doing
  • Quick reference
  • When do you actually implement one yourself?

You call .sort() every day without thinking about it. But the sorting algorithm your language's standard library actually uses is a carefully engineered hybrid - and understanding what it's doing (and why) makes you a better engineer. Not just for interviews. For writing better code, debugging weird performance cliffs, and knowing when to reach for something different.

This post covers the eight classic sorting algorithms - the ones that appear in every CS curriculum and show up in every whiteboard interview - with an honest take on what they're actually useful for in 2025.

✦

The two camps

Every classic sorting algorithm falls into one of two buckets:

Time complexityExamples
Brute forceO(n²) averageBubble, Selection, Insertion
Divide & conquerO(n log n) averageMerge, Quick, Heap

O(n²) sounds terrible, and at scale it is. But for arrays under ~50 elements, the constant factors often make the "slow" algorithms faster in practice - which is why Timsort (Python, JavaScript) and Introsort (C++) use insertion sort internally for small subarrays.

The other axis that matters: stable vs unstable. A stable sort preserves the relative order of equal elements. If you're sorting records by last name and want a secondary sort by first name to be preserved, you need stability. Quicksort is unstable. Merge sort is stable. This matters.

✦

1. Bubble Sort - the one everyone knows, no one uses

How it works: Compare adjacent elements, swap if out of order. Repeat until no swaps happen.

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

Complexity: O(n²) average and worst case, O(n) best case (already sorted)
Stable: Yes
Space: O(1)

Honest take: Bubble sort is for teaching, not production. The only place it wins is a nearly-sorted array where you add an early-exit optimization - it becomes O(n) on already-sorted data. But insertion sort does the same thing better.

That said: every CS interview will assume you know it. You should know it.

✦

2. Selection Sort - simpler, not better

How it works: Find the minimum element in the unsorted portion, swap it to the front. Repeat.

def selection_sort(arr):
    for i in range(len(arr)):
        min_idx = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[min_idx]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

Complexity: O(n²) in all cases - it always does the full scan
Stable: No (standard implementation)
Space: O(1)

Honest take: Selection sort makes fewer swaps than bubble sort - exactly n-1 swaps always - which matters if swapping is expensive (say, moving large records on disk). Otherwise it has no advantage. It's also not stable, which rules it out for anything that cares about order preservation.

✦

3. Insertion Sort - the underrated one

How it works: Build a sorted subarray from left to right, inserting each new element into its correct position.

def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and arr[j] > key:
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

Complexity: O(n²) worst case, O(n) best case (sorted data), O(nk) if each element is at most k positions from its sorted position
Stable: Yes
Space: O(1)

Honest take: This is the one people underestimate. Insertion sort is adaptive - it runs in O(n) on nearly-sorted data. It's also online, meaning it can sort a stream of data as it arrives. And it's the algorithm that Timsort (used by Python and JavaScript under the hood) falls back to for subarrays under 64 elements, because cache performance and constant factors make it beat merge sort at small sizes.

If you ever need to sort a small array yourself: use insertion sort.

✦

4. Shell Sort - insertion sort, but turbocharged

How it works: Generalization of insertion sort. Instead of comparing adjacent elements, compare elements h positions apart. Decreasing h toward 1.

def shell_sort(arr):
    n = len(arr)
    gap = n // 2
    while gap > 0:
        for i in range(gap, n):
            temp = arr[i]
            j = i
            while j >= gap and arr[j - gap] > temp:
                arr[j] = arr[j - gap]
                j -= gap
            arr[j] = temp
        gap //= 2
    return arr

Complexity: O(n log² n) with good gap sequences, though the exact bound depends heavily on the gap sequence used
Stable: No
Space: O(1)

Honest take: Shell sort is an interesting middle ground - better than insertion sort on large arrays, no extra memory needed, but it's been largely superseded by introsort and Timsort. Still worth knowing because the gap-sequence idea is non-obvious and comes up in other places (like some cache-oblivious algorithms).

✦

5. Merge Sort - the stable workhorse

How it works: Divide the array in half, recursively sort both halves, then merge them back.

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

Complexity: O(n log n) in all cases - guaranteed, no worst case
Stable: Yes
Space: O(n) - this is the catch

Honest take: Merge sort has guaranteed O(n log n) performance - unlike quicksort which can degrade to O(n²) on bad pivot choices. It's also stable. Those two properties make it the go-to for sorting linked lists, external sorting (data that doesn't fit in memory), and anywhere stability matters.

The downside is memory: it needs O(n) extra space. For large arrays in memory-constrained environments, that's a real cost.

Java uses a variant of merge sort (Timsort) for object arrays. The stable guarantee matters when you're chaining comparators.

✦

6. Quick Sort - the fast one (usually)

How it works: Pick a pivot, partition the array so everything smaller is to the left and everything larger is to the right, then recursively sort both sides.

def quick_sort(arr, low=0, high=None):
    if high is None:
        high = len(arr) - 1
    if low < high:
        pivot_idx = partition(arr, low, high)
        quick_sort(arr, low, pivot_idx - 1)
        quick_sort(arr, pivot_idx + 1, high)
    return arr

def partition(arr, low, high):
    pivot = arr[high]
    i = low - 1
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

Complexity: O(n log n) average, O(n²) worst case (sorted input with bad pivot selection)
Stable: No
Space: O(log n) average stack space

Honest take: Quicksort has the best average-case performance of any comparison sort in practice - the constant factors are small because it has great cache behavior and does comparisons in-place. That's why C's qsort and many other standard libraries are named after it.

The catch: it degrades to O(n²) on sorted or nearly-sorted input with naive pivot selection. Modern implementations use randomized pivots, median-of-three, or switch strategies entirely - which is how introsort was born.

Quicksort is fast. But never trust it on user-provided data without randomized pivots unless you want to be in a CVE.

✦

7. Heap Sort - the Linux kernel's pick

How it works: Build a max-heap from the array, then repeatedly extract the max element to build the sorted array.

def heap_sort(arr):
    n = len(arr)
    # Build max heap
    for i in range(n // 2 - 1, -1, -1):
        heapify(arr, n, i)
    # Extract elements
    for i in range(n - 1, 0, -1):
        arr[0], arr[i] = arr[i], arr[0]
        heapify(arr, i, 0)
    return arr

def heapify(arr, n, i):
    largest = i
    left, right = 2 * i + 1, 2 * i + 2
    if left < n and arr[left] > arr[largest]:
        largest = left
    if right < n and arr[right] > arr[largest]:
        largest = right
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]
        heapify(arr, n, largest)

Complexity: O(n log n) in all cases - including worst case
Stable: No
Space: O(1) - sorts in place

Honest take: Heap sort has the best worst-case guarantee among the in-place sorts: O(n log n) always, O(1) space. The Linux kernel uses it instead of quicksort for exactly this reason - when you can't tolerate O(n²) worst-case behavior, heapsort is the safe choice.

The downside: poor cache performance. Heap sort jumps around memory in a way that causes a lot of cache misses, which makes it slower in practice than quicksort or merge sort even though it has the same asymptotic complexity. You'll rarely use it directly, but knowing it exists and why is useful.

✦

8. Radix Sort - breaking the comparison barrier

How it works: Sort integers digit by digit, from least significant to most significant, using a stable sort (usually counting sort) at each pass.

def radix_sort(arr):
    max_val = max(arr)
    exp = 1
    while max_val // exp > 0:
        counting_sort_by_digit(arr, exp)
        exp *= 10
    return arr

def counting_sort_by_digit(arr, exp):
    n = len(arr)
    output = [0] * n
    count = [0] * 10
    for i in range(n):
        index = (arr[i] // exp) % 10
        count[index] += 1
    for i in range(1, 10):
        count[i] += count[i - 1]
    for i in range(n - 1, -1, -1):
        index = (arr[i] // exp) % 10
        output[count[index] - 1] = arr[i]
        count[index] -= 1
    for i in range(n):
        arr[i] = output[i]

Complexity: O(nk) where k is the number of digits - effectively O(n) for fixed-width integers
Stable: Yes
Space: O(n + k)

Honest take: Radix sort is the algorithm that breaks the O(n log n) lower bound - because it doesn't use comparisons. It works on the actual digit/bit structure of the data. For sorting large sets of integers, IP addresses, phone numbers, or any fixed-length data, radix sort can be dramatically faster than comparison sorts.

The catch: it only works on integers or things that can be mapped to integers (strings of fixed length, dates). It doesn't work on arbitrary comparable data. And the constant factors mean that for small arrays, comparison sorts win.

✦

What .sort() is actually doing

Here's the part that makes the above useful beyond theory:

LanguageAlgorithm usedNotes
PythonTimsortMerge sort + insertion sort hybrid. Stable. Since Python 2.3.
JavaScript (V8)TimsortSame algorithm, switched from quicksort in 2019 for spec compliance (stable sort required by ES2019+).
Java (objects)TimsortArrays.sort(Object[]) and Collections.sort().
Java (primitives)Dual-pivot quicksortArrays.sort(int[]). No stability needed, faster in practice.
C++ STLIntrosortQuicksort + heapsort fallback + insertion sort for small arrays. Unstable.
Linux kernelHeapsortChosen for guaranteed worst-case and no stack space.
SwiftTimsort

Timsort is worth understanding at a high level: it looks for already-sorted "runs" in the input, then merges them with merge sort. Real-world data is often partially sorted (think: appending to a sorted list, or data with timestamps). Timsort exploits this and can run in O(n) on such data, beating theoretical alternatives.

✦

Quick reference

AlgorithmBestAverageWorstSpaceStable
BubbleO(n)O(n²)O(n²)O(1)Yes
SelectionO(n²)O(n²)O(n²)O(1)No
InsertionO(n)O(n²)O(n²)O(1)Yes
ShellO(n log n)O(n log² n)O(n²)O(1)No
MergeO(n log n)O(n log n)O(n log n)O(n)Yes
QuickO(n log n)O(n log n)O(n²)O(log n)No
HeapO(n log n)O(n log n)O(n log n)O(1)No
RadixO(nk)O(nk)O(nk)O(n+k)Yes
✦

When do you actually implement one yourself?

Honestly? Almost never. But here's when the knowledge matters:

You need a stable sort in a language that doesn't guarantee one: Some older runtimes, embedded environments, or custom data structures. Reach for merge sort.

You're sorting a small array in a hot loop: Array.sort() has overhead from being general-purpose. For arrays under ~20 elements in a performance-critical path, inline insertion sort is faster.

You need guaranteed worst-case bounds and can't use O(n) space: Heapsort.

You're sorting integers and n is large: Radix sort. The O(n) vs O(n log n) difference shows up fast at a million+ items.

You're writing something that needs to handle adversarial input: Randomize your quicksort pivots. Or use merge sort. A sorted-input DoS via quicksort is a real vulnerability class.

✦

The mental model to take away: the "best" sorting algorithm doesn't exist - it depends on the constraints. Timsort is the best general-purpose answer for unknown data. Quicksort is fastest when you know the data is random. Merge sort wins when you need stability and memory isn't a constraint. Heapsort wins when you need worst-case guarantees in O(1) space. Radix sort wins when you're sorting integers at scale.

Knowing which to reach for, and why, is the whole point of learning them.

Related reading

computerscience

Frontend Linear Data Structures Deep Dive: Arrays, Stacks, Queues, and Linked Lists

The Big Picture Before diving into stacks, queues, and linked lists, it helps to know...

algorithms

A* Search Finally Clicked When I Drew the Grid

A practical developer-friendly explanation of heuristic search, Greedy Best-First Search, and A* using a grid example instead of textbook language.

networking

Network Address Calculation: The Subnet Math That Matters

A practical subnetting guide showing how to calculate a network address from an IP address and mask using binary math and simple examples.

originally published

This post first ran on dev.to. Comments and reactions live there.

Continue on dev.to
PreviousReact 19 Micro-Interactions Without Layout JankA practical React 19 micro-interactions guide focused on motion boundaries, CSS transitions, optimistic UI, reduced motion, and performance.
Back to all posts
NextFrontend Linear Data Structures Deep Dive: Arrays, Stacks, Queues, and Linked ListsThe Big Picture Before diving into stacks, queues, and linked lists, it helps to know...
Back to archive
open channel·say hi anytime · 2026
--:--:--EST
get in touch

Saw something here?Tell me about it.

It's a portfolio, not a service · but I read every note — drop a line if anything here resonated, or just to say hi.

Start a conversation

Newsletter

Get the occasional dispatch

Notes and logs from m4rkyu.com — short, dated, no noise. Unsubscribe anytime.

Work

Production builds, games, and visual archives.

  • Projects
  • Games
  • Archive
  • Logs

Resources

Daily-use tools and a personal link library.

  • Search
  • Latest
  • Tools
  • Links
  • Notes
  • Topics
  • RSS
  • JSON feed
  • Shop

Studio

Background, contact, and channels for collaboration.

  • About
  • Contact
  • Changelog
  • Colophon
  • Resumepending

Socials

Find me on the usual feeds.

  • GitHub
  • dev.to
  • Email

LinkedIn · Instagram · YouTube · soon

M4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYU
Crafted since 2024
ZhenXiao Mark YuZhenXiao Mark Yu
© 2026 ZhenXiao Mark Yu·Ontario, Canada·PrivacyTerms
  • Email
  • GitHub
  • dev.to
Built with Next.js 16 · React 19 · Tailwind 4

Built with Next.js 16 · React 19 · Tailwind 4