08. Implementations
An implementation is code that a reader can read, run and copy. It is written to be read first and to be fast enough second. Each implementation meets the contract of the problem that its algorithm solves (09.1), so one set of tests checks every language.
8.1 What an implementation is
- An implementation belongs to one
algorithmordata-structurenode and one language. It lives inimpl/<lang>/in the node directory (04.3). - It has one reference version. It can also have other versions with labels (8.6).
- It has its native tests next to it, in the test framework of its language (8.3). The shared cases of the contract run through a generated adapter (8.5, 09.2). The native tests show a reader how to call the code. The shared cases prove that it is correct.
- It has no dependency outside the standard library of its language (8.4).
- It follows the pseudocode of the page line by line, and marks the lines with
@linecomments, so that the visualization and the page can show the line that runs (8.3, 10.5). - The build computes its level I0 to I4 (8.8). Nobody writes the level.
8.2 The language register and the tiers
8.2.1 The register
registers/languages.yaml lists each language. A language is in the register before any implementation in it can merge (rule I12). Each entry has the fields of this example:
- id: rust # the directory name under impl/
name: Rust
tier: 1
ext: [rs]
file_case: snake # merge_sort.rs
ident_case: snake # fn merge_sort
type_case: pascal # struct BinaryHeap
test: {framework: builtin, file: "inline mod tests"}
format: {tool: rustfmt}
lint: {tool: clippy, args: ["-D", "warnings"]}
mutation: {tool: cargo-mutants}
image: algo-runner-rust # 8.7
int: i64 # the type of the contract type int (8.9)
list: "Vec<T> / &[T]"
comment: "//"
highlight: rust # the grammar of the site highlighter
since: 2026-11
8.2.2 The tiers
| Tier | Languages | Required for | Count |
|---|---|---|---|
| 1 | Python, C++, Java, C, C#, JavaScript, TypeScript, Go, Rust, Kotlin | each node of the core, and each node of the long tail that has an implementation at all | 10 |
| 2 | Swift, Ruby, PHP, Dart, Scala, Haskell, OCaml, F#, Elixir, Julia, R, Zig, Lua | each node of the core | 13 |
| Lean | Lean 4, the executable model of 07.3 | each node with a Lean part | 1 |
| 3 | open: for example Clojure, Erlang, Perl, Nim, Crystal, Fortran, Ada, Racket, Common Lisp, MATLAB/Octave, Prolog, Mojo, Gleam, D, Pascal, Visual Basic | nothing; a contributor adds them | any |
"All popular languages" means tiers 1 and 2: 23 languages plus Lean. Tier 3 has the same harness, the same tests and the same ladder. It is not in the "complete" test of 04.8 and not in the coverage levels of 16.2. The site shows tier-3 implementations in the language menu under "More languages".
8.2.3 The evidence for the choice
No single ranking measures what readers of an algorithm site want. The tiers combine four signals (01.5):
| Signal | What it says (2025–2026) |
|---|---|
| TIOBE index, October 2026 | 1 Python 19.23%, 2 C 11.33%, 3 C++ 8.87%, 4 Java 8.41%, 5 C# 3.79%, 6 JavaScript 2.77%, 7 Visual Basic 2.20%, 8 R 2.06%, 9 SQL 1.97%, 10 Rust 1.44%, 11 Go 1.39%; Julia is back at 18; Dart and OCaml are close to the top 20; TypeScript is near 39 |
| GitHub Octoverse 2025 | TypeScript became the most used language on GitHub by monthly contributors, ahead of Python and JavaScript |
| Stack Overflow Developer Survey 2025 | JavaScript, Python, SQL, TypeScript, Java, C#, C++, C, PHP, Go, Rust and Kotlin lead among general-purpose languages |
| teaching and practice | Python, Java, C and C++ dominate first courses; C++ dominates competitive programming, then Python and Java; interview sites use Python, Java, C++, JavaScript, Go, Kotlin, Swift and C# |
The rules of the choice:
- Tier 1 is each general-purpose language that is strong in at least two of the four signals. Kotlin is in tier 1 for its weight in interviews and on Android. Swift and PHP are just below the line, so they lead tier 2.
- Tier 2 is each language with a strong showing in one signal and a real community that reads about algorithms: the functional family (Haskell, OCaml, F#, Scala, Elixir), the scientific family (Julia, R), the systems newcomer (Zig), the scripting family (Ruby, PHP, Lua) and the app family (Swift, Dart).
- Not in tiers 1 or 2: SQL (not a general-purpose language; set-based algorithms such as joins are explained in SQL inside the prose of
systems/database-algorithms), Visual Basic (high on TIOBE, low everywhere else), MATLAB (proprietary; Octave can be tier 3). - The tiers are reviewed once a year, in November, against the four signals. A move between tiers is a pull request to the register with the numbers.
8.2.4 The pinned toolchains
manifests/toolchains.yaml pins one toolchain for each language. algo bump toolchains proposes newer versions each month, runs the full test matrix of the language with them, and opens a pull request with the report. The versions below are the first pins; the manifest is the truth.
python: {version: "3.14", test: pytest, format: ruff, lint: [ruff, mypy --strict]}
cpp: {compilers: [gcc 15, clang 21], std: c++23, flags: "-Wall -Wextra -Wpedantic -Werror", sanitizers: [address, undefined]}
c: {compilers: [gcc 15, clang 21], std: c23, flags: "-Wall -Wextra -Wpedantic -Werror", sanitizers: [address, undefined]}
java: {jdk: "25", build: gradle, test: junit 5}
kotlin: {version: "2.3", jvm: "25", test: kotlin.test}
csharp: {dotnet: "10", lang: "14", test: xunit, nullable: enable}
javascript: {node: "24", test: "node:test"}
typescript: {node: "24", tsc: "pinned in the image", strict: true, test: "node:test"}
go: {version: "1.27", test: testing, lint: [go vet, staticcheck]}
rust: {channel: stable, edition: "2024", test: builtin, lint: clippy}
lean: {toolchain: "leanprover/lean4:v4.35.0", mathlib: "v4.35.0"} # same pin as math2 (07.2)
Two compilers for C and C++ catch code that depends on one compiler. The sanitizers run on the shared cases and the random tests. An implementation that triggers a sanitizer report is at I0 for that run.
8.3 Conventions for each language
8.3.1 Rules for every language
- Read first. The reference version is the clearest correct code that meets the stated bound. A trick that saves a constant factor and costs clarity goes in the
optimizedversion (8.6). - Follow the pseudocode. The names of variables and procedures are the names of the pseudocode of the page (06.4), changed only to the case rule of the language. One procedure of the pseudocode is one function of the code. The order of the steps is the same.
- Mark the lines. Each line of the pseudocode that the page or the scene refers to has a marker comment on the line of code that does it:
# @line L4in Python,// @line L4in C++. A marker can be on more than one line of code. Rule I11 checks that each line id of the pseudocode has at least one marker, and that each marker names a line id that exists. - One entry point. The function or type that the contract names is the public entry point. Its name comes from the contract by the case rule of the language (8.3.3). Helpers are private where the language has private.
- No input or output. The implementation reads no file, no standard input and no environment, and it prints nothing. The adapter does the input and output (8.5).
- No global mutable state. Each call is independent. A cache that persists between calls is not allowed in the reference version.
- Preconditions are written down. The doc comment says what the input must satisfy (sorted, non-negative weights, a DAG). The behaviour on an input that breaks a precondition is the behaviour that the contract says: return an error value, raise, or "undefined, not tested".
- Comments explain why. The doc comment says what the function does, its cost, and a link to the page. Line comments say why a step is right or needed, never what the syntax does. The invariant of each loop of the proof is a comment above the loop, in the words of the proof (06.5).
- Short lines. At most 100 columns, 4 spaces or the format tool of the language. The formatter of the register decides; nobody argues about style.
- Size. A reference version is usually 10 to 80 lines without tests. Rule I13 warns above 200 lines, which often means that the code does too much or that the node should split.
8.3.2 The table of tools
| Language | File name | Entry name | Test framework | Formatter | Linter and checks |
|---|---|---|---|---|---|
| Python | merge_sort.py, test_merge_sort.py |
merge_sort |
pytest | ruff format | ruff, mypy --strict |
| C++ | merge_sort.hpp, merge_sort_test.cpp |
algo::merge_sort |
algo_test.hpp (8.4) |
clang-format | clang-tidy, -Werror, sanitizers |
| Java | MergeSort.java, MergeSortTest.java |
MergeSort.sort |
JUnit 5 | google-java-format | javac -Xlint:all -Werror |
| C | merge_sort.h, merge_sort.c, merge_sort_test.c |
merge_sort |
algo_test.h (8.4) |
clang-format | clang-tidy, -Werror, sanitizers |
| C# | MergeSort.cs, MergeSortTests.cs |
MergeSort.Sort |
xUnit | dotnet format | analyzers, warnings as errors, nullable |
| JavaScript | merge-sort.mjs, merge-sort.test.mjs |
mergeSort |
node:test | Biome | Biome lint |
| TypeScript | merge-sort.ts, merge-sort.test.ts |
mergeSort |
node:test | Biome | tsc --strict, Biome lint |
| Go | merge_sort.go, merge_sort_test.go |
MergeSort |
testing | gofmt | go vet, staticcheck |
| Rust | merge_sort.rs |
merge_sort |
#[cfg(test)] mod tests |
rustfmt | clippy -D warnings |
| Kotlin | MergeSort.kt, MergeSortTest.kt |
mergeSort |
kotlin.test | ktfmt | detekt |
| Swift | MergeSort.swift, MergeSortTests.swift |
mergeSort |
Swift Testing | swift-format | swift-format lint |
| Ruby | merge_sort.rb, merge_sort_test.rb |
merge_sort |
minitest | rubocop -a | rubocop |
| PHP | MergeSort.php, MergeSortTest.php |
mergeSort |
PHPUnit | php-cs-fixer | phpstan max |
| Dart | merge_sort.dart, merge_sort_test.dart |
mergeSort |
package:test | dart format | dart analyze |
| Scala | MergeSort.scala, MergeSortSuite.scala |
mergeSort |
munit | scalafmt | -Werror |
| Haskell | MergeSort.hs, MergeSortSpec.hs |
mergeSort |
hspec | fourmolu | hlint, -Wall -Werror |
| OCaml | merge_sort.ml, test_merge_sort.ml |
merge_sort |
alcotest | ocamlformat | warnings as errors |
| F# | MergeSort.fs, MergeSortTests.fs |
mergeSort |
Expecto | Fantomas | warnings as errors |
| Elixir | merge_sort.ex, merge_sort_test.exs |
MergeSort.sort |
ExUnit | mix format | credo --strict |
| Julia | merge_sort.jl, test_merge_sort.jl |
merge_sort |
Test (stdlib) | JuliaFormatter | Aqua checks |
| R | merge_sort.R, test_merge_sort.R |
merge_sort |
testthat | styler | lintr |
| Zig | merge_sort.zig |
mergeSort |
builtin test blocks |
zig fmt | zig build -freference-trace |
| Lua | merge_sort.lua, merge_sort_spec.lua |
merge_sort |
busted | StyLua | luacheck |
| Lean | lean/Algo/.../MergeSort.lean (07.3) |
Algo.Sorting.MergeSort.mergeSort |
#guard, lake test |
(none) | algo lean lint |
The register holds this table. algo new impl <slug> <lang> writes the two files with the right names, the licence header, the doc comment, the signature from the contract and empty bodies with @line markers for each pseudocode line.
8.3.3 The canonical signature
The contract of the problem (09.1) gives the signature in a neutral type language:
# nodes/searching/sequential-and-binary-search/lower-bound-problem/contract.yaml (part)
entry: lower_bound
params:
- {name: a, type: "list<int>", pre: "sorted non-decreasing"}
- {name: x, type: int}
returns: {type: int, post: "least i with a[i] >= x, or len(a)"}
generic: {T: ordered} # optional: the entry may be generic over T
bounds: {len(a): [0, 1000000], int: [-2^63, 2^63-1]}
The neutral types and their mapping:
| Neutral | Python | C++ | Java | C | C# | JS/TS | Go | Rust | Kotlin | Haskell |
|---|---|---|---|---|---|---|---|---|---|---|
int |
int |
std::int64_t |
long |
int64_t |
long |
number (safe range) or bigint (8.9.1) |
int64 |
i64 |
Long |
Int |
index |
int |
std::size_t |
int |
size_t |
int |
number |
int |
usize |
Int |
Int |
bool |
bool |
bool |
boolean |
bool |
bool |
boolean |
bool |
bool |
Boolean |
Bool |
float |
float |
double |
double |
double |
double |
number |
float64 |
f64 |
Double |
Double |
string |
str |
std::string_view in, std::string out |
String |
const char * + length |
string |
string |
string |
&str in, String out |
String |
String |
list<T> |
list[T] / Sequence[T] |
std::span<const T> in, std::vector<T> out |
long[] or List<T> |
pointer + length | ReadOnlySpan<T> / T[] |
T[] |
[]T |
&[T] in, Vec<T> out |
List<T> / LongArray |
[T] |
graph |
adjacency list[list[int]] |
std::vector<std::vector<int>> |
List<List<Integer>> |
CSR arrays | List<int>[] |
number[][] |
[][]int |
Vec<Vec<usize>> |
List<List<Int>> |
Array Int [Int] |
wgraph |
list[list[tuple[int, int]]] |
std::vector<std::vector<Edge>> |
List<List<Edge>> |
CSR + weights | List<(int, long)>[] |
[number, number][][] |
[][]Edge |
Vec<Vec<(usize, i64)>> |
List<List<Edge>> |
Array Int [(Int, Int)] |
option<T> |
T | None |
std::optional<T> |
OptionalLong / Optional<T> |
out param + bool |
T? |
T | null |
(T, bool) |
Option<T> |
T? |
Maybe T |
The register holds the full table for each language. Rule I14 checks that the entry point has the canonical signature after the mapping.
The case rule maps lower_bound to lower_bound (snake), lowerBound (camel) or LowerBound (Pascal, for Go exports and C# methods). Languages with classes put the entry in a class named after the algorithm (BinarySearch.lowerBound in Java and Kotlin, BinarySearch.LowerBound in C#).
8.3.4 Generics
- When the contract has
generic: {T: ordered}, a language with cheap and clear generics writes the entry generically: RustT: Ord, C++std::totally_ordered T, Gocmp.Ordered, Java<T extends Comparable<? super T>>, C#where T : IComparable<T>, SwiftT: Comparable, KotlinT : Comparable<T>, TypeScript with a comparator, HaskellOrd a, OCaml with acompareargument or a functor. - The adapter calls the generic entry with
intand withstringcases, so the generic code is tested on two types. - C has no generics. The C reference works on
int64_t. A Cqsort-style version with a comparator andvoid *is a variant with the labelgeneric(8.6). - Python, Ruby, JavaScript, Lua and the other dynamic languages are generic already. Python adds type hints with a
TypeVarbound to aSupportsLessThanprotocol written in the file, becausetypingis in the standard library.
8.3.5 Data structures
A data-structure node implements the operations of its abstract-type (04.5). The implementation is:
- a class (Python, Java, C#, C++, Kotlin, Swift, Ruby, PHP, Dart, Scala, TypeScript, JavaScript), a struct with methods (Go, Rust, Zig), a module with an abstract type (OCaml, Haskell, F#, Elixir, Julia), a table with functions (Lua) or an opaque struct with functions that take a pointer (C:
heap_new,heap_push,heap_free); - with one method for each operation of the abstract type, named after the operation by the case rule;
- with a constructor from nothing and, when the abstract type lists
build, from a list; - with no method that the abstract type does not list, except private helpers and, optionally,
check_invariant, which the tests call after each operation in debug runs.
Persistent structures in Haskell, OCaml, Elixir, F# and Scala return a new value from each update. Their contract is the same; the adapter threads the value through the sequence of operations (09.2).
8.4 The standard library only
Rule I07: an implementation imports nothing outside the standard library of its language, at the pinned version.
- The reason: a reader can copy the file into any project, and the code shows the whole algorithm. A dependency would hide the part that the page explains.
- The
libraryvariant (8.6) may call the standard library's own version (bisect.bisect_left,std::sort,slices.Sort). It still imports only the standard library. - Tests may use the one test framework of the register. The runner image has it at a pinned version. For C and C++, which have no standard test framework, the harness provides
algo_test.handalgo_test.hpp: one small header each, MIT, withCHECK,CHECK_EQand a test registry. It lives in algorithm-agent, andalgo gencopies it into the build tree. - Rule I07 reads the imports with the parser of each language (tree-sitter grammars in
algo) and compares them with the list of standard modules of the pinned version, which the runner image exports asstdlib.txt. - A data structure that the algorithm uses and that the standard library lacks (a priority queue with decrease-key, a disjoint-set forest) is written in the same file as a private helper, or the file imports the reference implementation of that node through the build tree. The second form is allowed only for nodes in
uses, and the site shows a link to the imported node next to the code.
8.5 Generated build trees
Implementations live with their algorithms. Build tools want one tree per language. algo gen <lang> makes that tree in build/lang/<lang>/ from each nodes/**/impl/<lang>/, and nobody edits it (rule I15: no file under build/ is committed).
| Language | The generated tree |
|---|---|
| Python | a package algo with one module for each node, algo.sorting.merge_sort; pyproject.toml; conftest.py with the case fixtures |
| C++ | one CMake project; one test target for each node, grouped by top area; the header algo_test.hpp |
| C | as C++, with one static library for each top area |
| Java | one Gradle build with one subproject for each top area; package algo.<area>.<slug without hyphens>; JUnit 5 |
| Kotlin | as Java |
| C# | one solution with one project for each top area and one test project for each; namespace Algo.<Area>.<Slug> |
| JavaScript | one directory of ES modules and a package.json with no dependencies; node --test |
| TypeScript | as JavaScript, plus a tsconfig.json with strict |
| Go | one module algo.local/impl with one package for each node, named after the slug without hyphens (mergesort) |
| Rust | one Cargo workspace with one crate for each top area; lib.rs declares each node as a module with #[path] |
| Lean | not generated; lean/ is its own Lake package (07.2) |
| others | the register gives a template for each, for example a dune-project for OCaml or a Package.swift for Swift |
Rules of the generator:
- Files are copied, not linked, so that the image can mount the tree read-only.
build/lang/<lang>/SOURCESmaps each generated path back to its node path.algo testrewrites each compiler error and test failure to the node path, so the author seesnodes/sorting/comparison/merge-sort/impl/rust/merge_sort.rs:14. - Where the language needs a package or namespace line in the file (Java, Kotlin, C#, Go, Scala), the node file carries it, and the generator checks that it matches the path (rule I12). The file still compiles alone after a reader copies it and fixes that one line.
- The generator also writes the adapter of each node: a small program in the language that reads cases as JSON lines on standard input, decodes them to the native types of 8.3.3, calls the entry point and writes the result as JSON. The adapter for C and C++ includes a tiny JSON codec. The protocol is in 09.2. The adapter is not part of the implementation and is not shown on the site.
- One build for each top area keeps compile times low and lets the matrix run areas in parallel (8.7). A change to one node rebuilds its area only.
8.6 Versions with labels
A node can have more than one version in a language. The reference version is in impl/<lang>/. Each other version is in impl/<lang>/<label>/.
| Label | What it is | Rules |
|---|---|---|
reference |
the default: clear, correct, within the stated bound | exactly one for each language; the I level is its level |
optimized |
faster in practice: iterative instead of recursive, a cutoff to insertion sort, branchless steps, cache-aware layout | the same contract and tests; the benchmark (09.7) must show a speedup of at least 1.3× on the largest size, or the version is removed (rule I16) |
functional |
idiomatic functional style in a language where the reference is imperative (a recursive Scala or Kotlin version, a fold in Python) | same contract; same tests |
library |
the call to the standard library's own version, with a note on its guarantees (std::sort is introsort, not stable; sorted in Python is Timsort, stable) |
one call and a note; tested on the shared cases |
generic |
a generic form where the reference is not generic (C with void * and a comparator) |
tested with two element types |
contest |
short code for competitive programming, with the usual shortcuts of that community | tested; shown only in the "Competitive programming" course and under a toggle |
The site shows the reference version first and a version menu next to the language menu (14.4). The label is in the file path, never in the code.
8.7 Runner images and the sandbox
8.7.1 Images
- Each language has one image,
ghcr.io/tamnd/algo-runner-<lang>, built fromalgorithm-agent/images/<lang>/Dockerfile. It holds the toolchain, the test framework, the formatter, the linter, the mutation tool (09.6), the benchmark helper andstdlib.txt. manifests/images.yamlin the content repository pins each image by digest, as in math2. A new image is a pull request that moves the digest and carries the report of the full test matrix of the language with it.- Images are built for
linux/amd64andlinux/arm64. The nightly build runs both, and a test that passes on one architecture and fails on the other is a finding (rule I17).
8.7.2 The sandbox
Each run of tests or benchmarks of an implementation runs in its image under these limits:
| Limit | Value |
|---|---|
| network | none (--network none) |
| file system | the build tree read-only; a tmpfs of 512 MiB for build outputs |
| user | an unprivileged user, no new privileges, the default seccomp profile |
| CPU | 2 cores for a test run, 1 pinned core for a benchmark run |
| memory | 2 GiB for each run, 256 MiB for each case batch unless the contract asks for more |
| time | 10 s for each batch of cases, 120 s for a native test suite, 600 s for a mutation run of one node |
| processes | at most 256 |
Code from a pull request runs only inside the sandbox, on the CI runners or the build server, never on a maintainer's machine through algo (rule I18: algo test refuses to run tests outside a container unless -unsafe-local is given).
8.7.3 The cache
- Build caches (Cargo target, the Go build cache, Gradle, the .NET cache) live in volumes keyed by the image digest. Dependencies of tests are inside the image, so builds are offline.
- A test result is cached under the hash of: the implementation files, the contract, the cases, the generator specs and seeds, the adapter, and the image digest. A cached pass is reused until one of them changes.
- The pull request check runs only the changed implementations and, when a contract or its cases change, every implementation of every algorithm that solves the problem (03.6).
8.7.4 Scale (estimate)
At 1,200 core nodes × 23 languages, plus the long tail in tier 1, the matrix has about 35,000 implementations. A cold full run on the build server (32 cores) is estimated at 3 to 5 hours, most of it compilation and mutation testing. A warm nightly run with the cache is estimated at under 30 minutes. M2 measures both (19).
8.8 The implementation ladder I0–I4
The build computes one level for each node and each language, from the reference version.
| Level | It needs | Rules |
|---|---|---|
| I0 | no reference version, or one that fails a check below I1 | |
| I1 | it builds with no warnings under the flags of the register, on both compilers where there are two, and passes every shared case of the contract (09.2) | I01, I02, I07, I10, I12, I14 |
| I2 | I1, and it passes the random and adversarial differential tests against the oracle (09.5), the overflow and deep-input cases (8.9), the sanitizers where they exist, and its native tests plus the shared cases kill at least 85% of the mutants (09.6) | I03, I04, I08, I09 |
| I3 | I2, and the formatter makes no change, the linter reports nothing, the @line markers are complete, and the complexity check passes (09.7) where the language is in the benchmark set |
I05, I06, I11 |
| I4 | I3, and a person who is not the author reviewed this exact file hash (13.4) |
The benchmark set is C++, Rust, Go and Java, where timing is stable enough. For the other languages, I3 needs the complexity check of the operation counters in 09.7 instead of wall-clock time.
A node is "tested everywhere" (13.1) when each tier-1 language is at I3 and each tier-2 language is at I2.
8.9 Numbers, overflow and recursion
8.9.1 Integers
- The contract type
intis a signed 64-bit integer. The contract givesboundsfor each input (8.3.3). An implementation must give the right answer for every input inside the bounds, with no overflow in any intermediate value (rule I08). - The shared cases of each contract include the extreme values of its bounds, so an implementation that writes
(lo + hi) / 2fails I2 when the indices can be large, and one that adds two weights fails when the sum can pass 2^63 − 1. - Python, Ruby, Haskell (
Integer), Elixir, Lisp-family and other languages with big integers by default still meet the bounds; the adapter checks that results fit in 64 bits. - JavaScript and TypeScript:
numberis exact up to 2^53 − 1. A contract whose values stay in that range usesnumber. A contract with full 64-bit values usesbigint, and the register says so for each contract. The adapter checks the range. - Modular arithmetic contracts (number theory) give the modulus. Multiplication of two residues near 2^63 uses
__int128in C and C++,u128in Rust,math/bits.Mul64in Go,Math.multiplyHighin Java, or a double-and-add helper written in the file.
8.9.2 Floats
A contract with float outputs gives an absolute and a relative tolerance, and the checker compares within them (09.3). Numerical algorithms state their error bound in the front matter, and a case that is outside the bound fails.
8.9.3 Recursion depth
- Recursion is allowed and often clearer. The reference version may recurse when the depth is O(log n) (merge sort, balanced trees) or bounded by a small constant.
- When the depth can be Θ(n) (DFS, naive quicksort, recursive DP on a path), the reference version must pass the deep input case of the contract, a path or a sorted list at the largest size (rule I09). Languages with small default stacks (Python, JavaScript, Java, C#, Kotlin) then use an explicit stack in the reference version, and keep the recursive form as a
functionalversion with a note on its limit. The page says why. - Languages with tail calls or large stacks (Scheme, Elixir, OCaml with OCaml 5 stacks, Haskell with lazy evaluation where it is safe) may keep the recursive form when it passes the deep input case.
- The adapter never raises the stack limit, so the result is the one a reader would get when they copy the code.
8.10 The Lean model as a language
The Lean model of 07.3 is an implementation too:
- It is executable.
lake buildmakes the binaryalgo-oracle, which reads cases as JSON lines, runs the model of the named node and writes the result. It is the oracle of the differential tests (09.5). - It has a tab on the page, "Lean", next to the other languages, with a link to the proof (14.4). It is written in the plain functional style that the proofs need, with
List,Array,NatandInt, and termination proved, notpartial. - Its level is the formal ladder L0–L4 (07.8), not the I ladder. It does not count as a tier-1 or tier-2 language.
- When the model is too slow to be an oracle at the largest sizes (a list-based model of an array algorithm), the contract marks the sizes above which the oracle is the Python reference instead, and the build first checks the Python reference against the Lean oracle on the smaller sizes (09.5).
8.11 The licence header
Each implementation and test file starts with two comment lines:
SPDX-License-Identifier: MIT
Part of https://algorithm.tamnd.com/merge-sort/. The authors are listed in prov/4MRG7S.jsonl.
Rule I10 checks them. The MIT licence text is in LICENSES/MIT.txt, and REUSE.toml gives MIT to each impl/ path (17.1). Copying the file with the header meets the licence. The header has no year and no list of names, so it never changes when the code changes.
8.12 A worked example: lower bound in four languages
The page binary-search has this pseudocode (06.4), with line ids:
LOWER-BOUND(a, x)
L1 lo ← 0; hi ← n
L2 while lo < hi
L3 mid ← lo + ⌊(hi − lo) / 2⌋
L4 if a[mid] < x then lo ← mid + 1
L5 else hi ← mid
L6 return lo
The loop invariant of the proof is: every index below lo holds a value less than x, and every index at or above hi holds a value at least x.
Python: impl/python/binary_search.py
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/binary-search/. The authors are listed in prov/2B7QDX.jsonl.
"""Binary search for the first position whose value is at least x."""
from collections.abc import Sequence
def lower_bound(a: Sequence[int], x: int) -> int:
"""Return the least i with a[i] >= x, or len(a) if there is none.
a must be sorted in non-decreasing order. Time O(log n), extra space O(1).
"""
lo, hi = 0, len(a) # @line L1
# Invariant: a[i] < x for i < lo, and a[i] >= x for i >= hi.
while lo < hi: # @line L2
mid = lo + (hi - lo) // 2 # @line L3
if a[mid] < x: # @line L4
lo = mid + 1
else: # @line L5
hi = mid
return lo # @line L6
impl/python/test_binary_search.py
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/binary-search/. The authors are listed in prov/2B7QDX.jsonl.
from binary_search import lower_bound
def test_finds_first_of_equal_values() -> None:
assert lower_bound([1, 2, 2, 2, 5], 2) == 1
def test_returns_length_when_all_smaller() -> None:
assert lower_bound([1, 2, 3], 9) == 3
def test_empty() -> None:
assert lower_bound([], 0) == 0
C++: impl/cpp/binary_search.hpp
// SPDX-License-Identifier: MIT
// Part of https://algorithm.tamnd.com/binary-search/. The authors are listed in prov/2B7QDX.jsonl.
#pragma once
#include <concepts>
#include <cstddef>
#include <span>
namespace algo {
// The least i with a[i] >= x, or a.size() if there is none.
// a must be sorted in non-decreasing order. Time O(log n), extra space O(1).
template <std::totally_ordered T>
std::size_t lower_bound(std::span<const T> a, const T& x) {
std::size_t lo = 0, hi = a.size(); // @line L1
// Invariant: a[i] < x for i < lo, and a[i] >= x for i >= hi.
while (lo < hi) { // @line L2
std::size_t mid = lo + (hi - lo) / 2; // @line L3
if (a[mid] < x) { // @line L4
lo = mid + 1;
} else { // @line L5
hi = mid;
}
}
return lo; // @line L6
}
} // namespace algo
Go: impl/go/binary_search.go
// SPDX-License-Identifier: MIT
// Part of https://algorithm.tamnd.com/binary-search/. The authors are listed in prov/2B7QDX.jsonl.
// Package binarysearch finds the first position whose value is at least x.
package binarysearch
import "cmp"
// LowerBound returns the least i with a[i] >= x, or len(a) if there is none.
// a must be sorted in non-decreasing order. Time O(log n), extra space O(1).
func LowerBound[T cmp.Ordered](a []T, x T) int {
lo, hi := 0, len(a) // @line L1
// Invariant: a[i] < x for i < lo, and a[i] >= x for i >= hi.
for lo < hi { // @line L2
mid := lo + (hi-lo)/2 // @line L3
if a[mid] < x { // @line L4
lo = mid + 1
} else { // @line L5
hi = mid
}
}
return lo // @line L6
}
Rust: impl/rust/binary_search.rs, with its tests inline
// SPDX-License-Identifier: MIT
// Part of https://algorithm.tamnd.com/binary-search/. The authors are listed in prov/2B7QDX.jsonl.
//! Binary search for the first position whose value is at least x.
/// Returns the least `i` with `a[i] >= x`, or `a.len()` if there is none.
///
/// `a` must be sorted in non-decreasing order. Time O(log n), extra space O(1).
pub fn lower_bound<T: Ord>(a: &[T], x: &T) -> usize {
let (mut lo, mut hi) = (0, a.len()); // @line L1
// Invariant: a[i] < x for i < lo, and a[i] >= x for i >= hi.
while lo < hi {
// @line L2
let mid = lo + (hi - lo) / 2; // @line L3
if a[mid] < *x {
// @line L4
lo = mid + 1;
} else {
// @line L5
hi = mid;
}
}
lo // @line L6
}
#[cfg(test)]
mod tests {
use super::lower_bound;
#[test]
fn finds_first_of_equal_values() {
assert_eq!(lower_bound(&[1, 2, 2, 2, 5], &2), 1);
}
#[test]
fn returns_length_when_all_smaller() {
assert_eq!(lower_bound(&[1, 2, 3], &9), 3);
}
#[test]
fn empty() {
assert_eq!(lower_bound::<i64>(&[], &0), 0);
}
}
The four files have the same shape, the same names, the same invariant comment and the same six markers. The site shows them in tabs, with the active pseudocode line highlighted in the code of the reader's language during the visualization (10.5). The library versions are one line each: bisect.bisect_left(a, x), std::ranges::lower_bound(a, x) - a.begin(), sort.Search(len(a), func(i int) bool { return a[i] >= x }) and a.partition_point(|v| v < x).
8.13 The rules of this document
The rules are listed in 13.3. In short:
| Rule | Checks |
|---|---|
| I01 | it builds with no warnings under the flags of the register |
| I02 | it passes each shared case of the contract |
| I03 | it passes the random and adversarial differential tests against the oracle |
| I04 | its tests kill at least 85% of the mutants |
| I05 | the formatter makes no change and the linter reports nothing |
| I06 | the complexity check passes |
| I07 | standard library only |
| I08 | no overflow inside the contract bounds (the extreme cases pass) |
| I09 | the deep-input case passes |
| I10 | the licence header is present |
| I11 | the @line markers cover the pseudocode line ids and name only existing ids |
| I12 | the language is in the register, and the file names and the package line follow its conventions |
| I13 | warning: a reference version is longer than 200 lines |
| I14 | the entry point has the canonical signature of the contract |
| I15 | no generated file under build/ is committed |
| I16 | an optimized version is at least 1.3× faster on the largest benchmark size |
| I17 | the results are the same on amd64 and arm64 |
| I18 | tests of a pull request run only in the sandbox |