
What "ten sorts in ten languages" say about the abstraction we're living through
October 28th, 2026 • ~5,000 words • 20 minute read
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
October 28th, 2026 • ~5,000 words • 20 minute read
The title is intentionally provocative :)
You probably do understand your code. Or at least, I'm sure you think you do. I like to think I do too!
But before we go any deeper into the premise behind the title, let's play a fun little game.
Do you think you understand the code below?
nums = [5, 3, 10, 1, 9] words = ["pear", "Apple", "fig", "banana"] print(sorted(nums)) # [1, 3, 5, 9, 10] print(sorted(words)) # ['Apple', 'banana', 'fig', 'pear']
Java
int[] nums = {5, 3, 10, 1, 9}; String[] words = {"pear", "Apple", "fig", "banana"}; Arrays.sort(nums); // [1, 3, 5, 9, 10] Arrays.sort(words); // [Apple, banana, fig, pear]
C
int cmp(const void *a, const void *b) { return (int)a - (int)b; } int nums[] = {5, 3, 10, 1, 9}; qsort(nums, 5, sizeof(int), cmp); // 1 3 5 9 10
C++
std::vector<int> nums = {5, 3, 10, 1, 9}; std::vectorstd::string words = {"pear", "Apple", "fig", "banana"}; std::sort(nums.begin(), nums.end()); // 1 3 5 9 10 std::sort(words.begin(), words.end()); // Apple banana fig pear
Go
nums := []int{5, 3, 10, 1, 9} words := []string{"pear", "Apple", "fig", "banana"} slices.Sort(nums) // [1 3 5 9 10] slices.Sort(words) // [Apple banana fig pear]
Rust
let mut nums = vec![5, 3, 10, 1, 9]; let mut words = vec!["pear", "Apple", "fig", "banana"]; nums.sort(); // [1, 3, 5, 9, 10] words.sort(); // ["Apple", "banana", "fig", "pear"]
Ruby
[5, 3, 10, 1, 9].sort # => [1, 3, 5, 9, 10] ["pear", "Apple", "fig", "banana"].sort # => ["Apple", "banana", "fig", "pear"]
Swift
let nums = [5, 3, 10, 1, 9].sorted() // [1, 3, 5, 9, 10] let words = ["pear", "Apple", "fig", "banana"].sorted() // ["Apple", "banana", "fig", "pear"]
PHP
$nums = [5, 3, 10, 1, 9]; $words = ["pear", "Apple", "fig", "banana"]; sort($nums); // [1, 3, 5, 9, 10] sort($words); // ["Apple", "banana", "fig", "pear"]
C#
int[] nums = {5, 3, 10, 1, 9}; string[] words = {"pear", "Apple", "fig", "banana"}; Array.Sort(nums); // 1 3 5 9 10 Array.Sort(words); // Apple banana fig pear
Okay, you might not know all of these languages fluently, but you could probably follow that these are all different ways of sorting lists (or arrays, or vectors, or slices, or whatever the language calls the concept of a collection of data). You're smart like that. That's why I like you.
I'd say, given just the programs we've shared so far, you might even feel like you could say you "understand" the code, even if it's not in a language you're particularly comfortable in.
Now a more interesting question—could you tell me which implementation is faster, and why?
The answer is always some flavor of "it depends."
But I could tell you pretty definitively that Python is going to be faster than C++ here, in a Big-O sense, when the list you hand it is already sorted or nearly sorted. Think of appending a few rows to an already-ordered log and sorting the whole thing again. (Reality tends to wash a lot of this out and make it not matter. A million already-sorted integers in C++ still finishes before you can blink.)
Why is that? Because Python's sorted() uses Timsort, which notices runs that are already in order and finishes in O(n) on sorted input, while C++'s std::sort uses introsort, a quicksort hybrid that does its full O(n log n) of work whether the input needed it or not. I was curious and measured it: on a sorted array of a million integers, Python's sort makes 999,999 comparisons. GCC's std::sort makes 25.6 million.
Except... I wrote that paragraph, went to check it on my Mac, and it was wrong! Apple's clang ships a different C++ standard library, LLVM's libc++, whose introsort notices when a partition comes out already ordered and finishes with a bounded insertion pass. Two million comparisons. Linear. Same std::sort, same source file, different answer, depending on which compiler happened to be on the machine.
Here's the thing to takeaway though: of those ten snippets—all running some flavor of a stock sorting function or method on two different sets of data—no two run the same algorithm.
Hold that thought. We'll come back to it.
// Example 1 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 2 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 3 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 4 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 5 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 6 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 7 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 8 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 9 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
// Example 10 var scores = new Array(40, 100, 1, 5, 25, 10); scores.sort(function (a, b) { return a - b; });
Okay, you probably feel even more confident you understand this code. (Sincerely though—why is that?). Or you might think copy/paste is broken on my machine and I published this without proofreading. It looks like the same code snippet copied ten-times over.
To many of you, you probably clocked that as a classic JavaScript sort. It's looked the same since 1997.
You even remembered the comparator, so you've clearly been bitten before! Omit it and every engine since 1997 will hand you back 1, 10, 100, 25, 40, 5, which is how nobody has ever sorted anything ever, because the default compares everything as strings. That one line has been true for thirty (!!) years and still catches people every week.
But you knew that. You understand this code.
Now can you tell me... which one is faster?
Oh wait, I cheated a little bit. I forgot to mention some things:
(And I left one out. Rhino, JavaScript on the JVM, started with quicksort in 1999, switched to heapsort in January 2002 "to follow JS," and since 2007 just hands the array to Java's Arrays.sort. Which is Timsort. Which is Java's port of Python's algorithm. JavaScript, sorted by Java, using Python's idea.)
So the answer to which JavaScript sort is faster comes down to some combination of where it executed and when in history. The code you wrote in 1999, left untouched and executing god knows what today on some cute little esoteric corner of the web, is almost certainly fundamentally different under the hood. If it lived in the Netscape lineage, it started as quicksort in 1999, took a detour through heapsort in 2002 and has been running merge sort since Firefox 3 in 2008. If it lived in Chrome, it was insertion sort or quicksort (depending on the size of your array!) for a decade and has been Timsort since September 2018.
My favorite version of this is Node 10 and Node 11: same program, same company, one release apart, different algorithm. Provided, of course, that nobody has since opened it on a phone, inside a React Native app, or on a Cloudflare Worker.
June 2019 was an interesting time in sort history. ES2019 made Array.prototype.sort stable at the spec level. Before that the spec said outright that the sort didn't need to be. V8 got there in September 2018, closing what its own team calls one of the oldest tickets in its tracker, and Chakra was the last major holdout. For about twenty years before that, your code didn't change.
Your understanding of it probably didn't change either. The thing underneath did, repeatedly.
But you still "understood the code" along the way there... right?
And it's not entirely settled even now. The spec says what order the results come out in. It says nothing about the order the comparisons happen in. Hand any of those ten modern engines a comparator with a side effect, a counter or a log line or a mutation, and you can watch them disagree with one another today.
I really could not get enough of this and kept digging. Here are some other languages have had similar journeys:
Here's the whole table for the ten snippets up top:
To the extent that the history of computing and programming is a history of abstractions piled on-top of abstractions, each one a deliberate trade of direct understanding for leverage, the LLM/AI transition we are living through today is just the latest chapter. One that's an order of magnitude leap in abstraction, but the same bargain nonetheless.
In many circles there are conversations happening and concerns being raised about whether or not we are writing code we still understand. These are good conversations for to have! We should be having them. But I think it's also worth revisiting and recalibrating our sense of what understanding even means or ever meant.
If you insist that every line of code must be understood, I am going to insist your own software has always depended on layers you almost certainly never personally examined. That, on some level—and in the least accusatory way possible—you never fully understood it.
And that is okay! What we are circling here is where to draw the line. There are still real differences between understanding an interface, understanding a system's behavior, being able to reason about how it fails, and merely having watched it succeed on the happy path.
So let me say the uncomfortable half plainly: it is fine that we don't fully understand the code we're writing with AI today, because we never understood it as well as we thought we did. We have always built things we couldn't explain all the way down. There are many layers of understanding between throwing out code like incantations until you get results and being able to perform brain surgery on the rocks we have tricked into thinking.
Understanding is not obsolete, but complete understanding was never the baseline. AI doesn't introduce the gap between what we can build and what we can comprehend. It moves the boundary. The line has almost certainly moved, and we are (collectively as an industry, as teams, as individuals) trying to figure out where the line should be. Like the history of every other decision in tech, where that line sits "depends" on many things.
One thing I am feeling with confidence lately, though: we overestimate how well we understand the code we have already written (without AI). We always have. Nothing in the examples above involved an LLM. That was us, typing sort() by hand and shipping an algorithm we couldn't name for twenty years, while feeling like we understood it the whole time. We understood it at a particular level of abstraction. The layers beneath were always somebody else's problem, something we chose not to think about or simple never considered.
You don't understand every instruction your compiler emits. You don't understand every operation your database performs. You don't understand every line of your runtime. You probably don't understand every line of your own code six months after you wrote it. And yet you build things, often very successfully.
(To that one person reading the last paragraph and saying "I do!" as each turn—congrats. Sit down and have a cookie.)
To be clear, I'm not suggesting we need to understand every implementation detail of the software we use, or that relying on abstractions is some kind of failure. Quite the opposite! Abstraction is how any of this works at all (gestures around at society, technology and progress generally). Nobody should have paused building a 1999 web page to audit SpiderMonkey's pivot selection.
What I am suggesting is that we're not very good at telling the difference between understanding a system well enough to use it and understanding it as thoroughly as we think we do. We never have been.
Those two kinds of "understanding" feel identical from the inside, which makes t
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。