M4RKYU.SYSEdition 2027
Skip to content
LOCZH/安大略 · 加拿大/▸logs · js sorting algorithms every developer should know 1hol待机OK/--:--:--EST
M4M4RK_YUportfolio
  • 创作
    创作Overview
    • 作品精选案例与项目记录
    • 游戏可玩原型与游戏开发日志
  • 影像
    影像Overview
    • 照片影像合集与视觉实验
    • 商店印刷品、海报和限量物件
  • 写作
    写作Overview
    • 博客长篇开发日志与现场笔记
    • 笔记短观察、链接与代码片段
  • 资源
    资源Overview
    • 工具38 款浏览器内开发工具
    • 链接每日使用的开发与设计书签
  • 关于
  • 联系
EN

同步 · 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...

发布日期
Jun 16
·
阅读时长
9 min read
·
点赞
5
algorithmsbeginnerstutorialjavascript
在 dev.to 查看

本页目录

  • 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.

相关阅读

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.

原文发布

本文首发于 dev.to,评论与点赞保留在原站。

在 dev.to 继续阅读
上一篇React 19 Micro-Interactions Without Layout JankA practical React 19 micro-interactions guide focused on motion boundaries, CSS transitions, optimistic UI, reduced motion, and performance.
返回全部文章
下一篇Frontend 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...
返回档案
频道开放·随时打个招呼 · 2026
--:--:--EST
联系

看到什么有意思的?和我聊聊。

这是一个作品集,不是服务 · 但每一条留言我都会看 — 如果哪里让你有所触动,或者只想打个招呼,欢迎写信过来。

开启对话

订阅

偶尔收到一封简讯

来自 m4rkyu.com 的笔记与日志——简短、标注日期、没有杂音。随时可退订。

作品

线上发布、游戏作品与视觉档案。

  • 项目
  • 游戏
  • 档案
  • 日志

资源

每日好用的工具与个人收藏的链接库。

  • 搜索
  • 最新
  • 工具
  • 链接
  • 笔记
  • 主题
  • RSS
  • JSON Feed
  • 商店

工作室

背景、联系方式以及合作渠道。

  • 关于
  • 联系
  • 更新日志
  • 技术说明
  • 简历筹备中

社交

在常去的平台上找到我。

  • GitHub
  • dev.to
  • 邮件

领英 · Instagram · YouTube · 敬请期待

M4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYUM4RKYU
始于 2024
ZhenXiao Mark YuZhenXiao Mark Yu
© 2026 ZhenXiao Mark Yu·加拿大 安大略·隐私条款
  • 邮件
  • GitHub
  • dev.to
由 Next.js 16 · React 19 · Tailwind 4 构建

由 Next.js 16 · React 19 · Tailwind 4 构建