Each CoderPuzzle problem is one directory named <zero-padded id>_<slug>.
A problem-set root holds those directories either directly or one level
down, inside an inclusive id-range shard of 100 (<lo>-<hi>, e.g.
0001-0100). This repository's five exemplars sit directly under
problems/. A full bank uses the shard layout below. The bundle directory
name, not the shard, is the problem key:
problems/
└── 0001-0100/ inclusive id-range shards of 100
└── 0001_two-sum/
├── problem.json machine data: metadata, invocation, limits
├── cases.json testcase corpus ({public, hidden} display grouping)
├── statement.md the human-readable problem statement
├── starter.py generated — never handcrafted
├── starter.java generated
├── starter.cpp generated
├── starter.go generated
├── starter.rs generated
├── starter.js
├── starter.ts
├── solution.py authored — must match the starter signature and pass
├── solution.java every case in cases.json
└── ...
Starters are always generated from problem.json by
scripts/gen_starters.py. Editors handwrite only non-derived content: the
statement, the invocation schema, the cases, and the solution bodies.
Solutions are authored on top of the generated starters.
{
"schema_version": 2,
"reference_solution": "",
"id": 1,
"slug": "two-sum",
"title": "Two Sum",
"difficulty": "Easy",
"tags": ["Array", "Hash Table"],
"topics": ["Array", "Hash Table"],
"type": "Algorithms",
"invocation": { "...": "see below" },
"limits": { "time_ms": 1500, "memory_mb": 256, "output_kb": 64 }
}idmatches the numeric directory prefix;slugandtitlematch the directory and the statement's# Titleheading.reference_solutiondesignates the time-cost baseline:""names the canonicalsolution.<ext>files, a variant slug namessolution_<variant>.<ext>. It is always the optimal approach — the section the worst-to-bestsolutions.mdordering ends with — and the judge runs exactly this one reference alongside the submission when scoring the time-cost percentage.difficultyis the original source difficulty — one ofEasy,Medium,Hard, mirrored from the upstream crawl catalog (never a re-evaluation);tagsis a non-empty array of strings.topicsis a non-empty array naming the techniques the problem tests, from the upstream crawl's LeetCode topic vocabulary; it classifies the bank for browsing and is not consumed by the judge.typeis one ofAlgorithms(programming-language bundles),Database(SQL), orShell, mirroringinvocation.type.invocation.typeisfunction,sql,shell,design,interactive, orconcurrent.
LeetCode-style, with a neutral value_type tree shared by every language:
{
"type": "function",
"class_name": "Solution",
"method": "twoSum",
"parameters": [
{
"name": "nums",
"codec": "json",
"value_type": {
"kind": "array",
"items": { "kind": "integer", "bits": 32 }
}
},
{
"name": "target",
"codec": "json",
"value_type": { "kind": "integer", "bits": 32 }
}
],
"return_codec": "json",
"return_type": {
"kind": "array",
"items": { "kind": "integer", "bits": 32 }
},
"entrypoints": { "go": "twoSum", "rust": "two_sum", "typescript": "twoSum" },
"comparison": "exact"
}kindis one ofinteger(bits32 or 64),number(finite float),boolean,string(UTF-8),array(withitems),linked_list,binary_tree,nary_tree,quad_tree,nested(NestedInteger),next_tree(parent/next tree),circular_list,doubly_circular,multi_list(LC 430 child chains),alias_list(LC 160 intersection, with a non-negativealiasnaming the parameter it splices into),graph,random_list(each optionally naming a providedclass),doubly_list(LC 3263, optional providedclass),doubly_list_node(LC 3294, wire{"values": [...], "node": v}, optional providedclass),random_tree(LC 1485, optional providedclass),special_tree(LC 2773 leaf ring on aTreeNodedisplay),nary_tree_nodes(LC 1506 node-list handover),nary_tree_ref(LC 1516, value +aliasnaming thenary_treeparameter it resolves into),json(generic any-shaped value; JavaScript/TypeScript only),instance(design-replay only: a parameter receiving another live instance of the design class — LC 1570'sdotProduct(vec)— carried on the wire as{"$ref": handle}; never appears in a positional argument list), orstruct(a provided record class with declaredfields). The node kinds take integeritems(32-bit by convention across these node types) and may omit the implied spec. The wire formats and per-kind serialization invariants are documented in docs/CODECS.md.entrypointsoverride the entry name per language (Go/Rust/TypeScript follow their casing conventions; Python/Java/C++/JavaScript usemethod). Keying differs by invocation type: function invocations use plain language keys ({"go": "twoSum"}); design invocations key per method ({"go.solve": "Solve"}) because one design class carries many methods; interactive invocations use plain language keys again (one entry per language).comparisonisexact(default),sorted,multiset, orset. Anything butexactmust be justified by the statement ("in any order").
{
"type": "sql",
"parameters": [{ "name": "dataset", "codec": "sql_setup" }],
"return_codec": "rows",
"comparison": "set"
}The table DDL lives in invocation.sql.schema; each case's dataset value
seeds the tables with INSERT statements.
{ "type": "shell", "comparison": "exact" }The submission is a bash script; each case's input is the raw file text
fed on stdin, and the script's stdout (trailing newlines stripped) is the
expected value — stored without its trailing newline. Starters are
starter.sh only, solutions solution*.sh; wire details live in
docs/CODECS.md.
Design problems declare class_name, constructor.parameters, and
methods; each case's input is an {"actions": [...], "params": [...]}
sequence. Interactive problems declare parameters, provided.oracle
(class, construct, auxiliary), and query_limit; an out-buffer
parameter carries out_buffer.capacity_from. Both run in every language
the bundle offers. Full wire contracts, the per-language oracle
construction table, and the statistical/validator judging modes live in
docs/CODECS.md.
Every class a problem's wire needs — ListNode, TreeNode, and the
rest of the docs/CODECS.md wire→class table, a named
graph/list node, a struct record, a design class's helper types, an
interactive oracle — ships as source under the bundle's own
provided/<language>/. There is no shared library: the judge holds no
predefined data structures of its own, so every bundle is
self-contained. Copy a well-known type's shape from a sibling bundle
using the same kind — never hand-invent one, never share a definition
across bundles. These sources are problem-set content (see
docs/TRUST-BOUNDARIES.md), assembled into every submission by
the judge, and they follow each language's assembly rules (Rust sources
use fully-qualified paths and no use lines; positional construction
matches declared field/parameter order in every language).
{
"public": [{ "input": [[2, 7, 11, 15], 9], "expected": [0, 1] }],
"hidden": [{ "input": [[3, 2, 4], 6], "expected": [1, 2] }]
}input is the positional argument array (design problems use an
{"actions": [...], "params": [...]} sequence instead). public cases are
the statement's examples and are shown in the problem pane; hidden cases
are everything else. The grouping is display-only — all case data is public
by design. This is a self-hosted, self-motivated learning framework:
nothing is secret. Every expected value must be produced by running a
reference solution, never hand-computed. A bundle normally has at least ten
cases across the two display groups. A smaller suite is valid when it exhausts
a finite single-integer input domain.
Pure prose, one required grammar:
# <Title> required — no numbering
## Description required
### Example N required (N counts from 1), a ```text block
### Constraints required, bullet list (#### Constraint N optional)
### Follow-up optional
## Hints optional
### Hint 1, ### Hint 2, … hints, when present, use this form
The # <Title> heading must equal problem.json's title. Example inputs
must correspond one-to-one, in order, to the public cases. SQL problems
state their contract as the schema DDL, so ### Constraints is optional for
them and required for every function problem.
scripts/gen_starters.py writes starter.* from problem.json; the file
extension selects the language (py, js, ts, java, cpp, go,
rust (rs), sql, sh). The set of starter.* files defines the
languages the problem offers. Never edit a starter by hand — change
problem.json and regenerate.
Every problem must carry solution.<ext> for every starter.<ext>. A
solution matches its starter's signature exactly and must pass every case in
cases.json; scripts/check.py and coderpuzzle judge run the whole
corpus, while a served submission judges a bounded subset of it
(docs/api-and-cli.md).
Hidden-type definition comments
Starters for problems whose wire carries hidden data structures
(docs/CODECS.md: ListNode, TreeNode, graph/random-pointer nodes,
NodeWithNext, MultiListNode, QuadNode, NestedInteger, ...) open with the
LeetCode-style commented-out definition of exactly the types that problem
uses — a ListNode-only problem never mentions TreeNode — in each offered
language: python # lines, rust // lines, and /** ... */ blocks for
java/cpp/go/javascript/typescript. The definitions mirror the bundle's
provided/<language>/ sources (the judge assembles those with every
submission) set in LeetCode's comment style, so a starter reads exactly like
the original template a solver remembers. The same comment block leads every
solution*.ext, so the Solutions tab shows the hidden types next to the
code that uses them. Both sides are enforced: check.py fails a starter
that is not generator output and a solution that does not open with its
starter's definition block (or that carries a stale one in a bundle with no
hidden types). Python starters also import exactly the typing names their
annotations render — Optional/List appear only when used, Callable
only for release callbacks — and python solutions follow the same rule: a
from typing import ... line names only what the file actually uses.
Every file in the repository is formatted by one toolchain, owned by the
coderpuzzle runner image: the implementation and every formatting pin
(widths, dialects, line length) live in this repo's runner/formatters.py —
the same source the ghcr.io/coderpuzzle/coderpuzzle image is built from —
and this repo's scripts/format.py is only a loader that imports that
implementation, in this order: (1) $CODERPUZZLE_RUNNER_DIR (explicit
override), (2) the loader's own directory (a fallback for a flattened
layout — format.py never sits beside formatters.py in the image),
(3) the image's /runner/formatters.py (CI and local docker runs with a
mounted checkout), (4) this checkout's own runner/formatters.py, which is
what plain local runs use. A sibling coderpuzzle checkout is only the
legacy pre-merge layout this loader once served — FORMAT.md and the runner
now live in this one repo. Generation (gen_starters.py), checking
(check.py), CI, the editor's Format button, and the coderpuzzle format CLI
all format through that single module, so output is byte-identical
everywhere. The npm package versions are pinned in runner/Dockerfile and
mirrored in the tracked scripts/problems-tooling/package.json, whose
gitignored node_modules/.bin is what format.py adds to PATH for
local runs — so a local npm install cannot drift the gate, and local
output stays byte-identical to the image.
| Files | Formatter | Language key in formatters.py |
|---|---|---|
*.py |
ruff format (line length 120) |
python3 |
*.go |
gofmt |
go |
*.rs |
rustfmt --edition 2021 (width 120) |
rust |
*.cpp |
clang-format (LLVM style, indent 4) |
cpp |
*.js |
prettier |
javascript |
*.ts |
prettier |
typescript |
*.java |
prettier + prettier-plugin-java |
java |
*.sql |
sql-formatter (sqlite dialect) |
sql |
*.sh |
shfmt (-ln bash -i 4) |
shell |
*.json |
canonical 2-space JSON + trailing newline | json |
*.md |
prettier (proseWrap: preserve) |
markdown |
Tool versions are pinned in the runner image's Dockerfile (the same build
the editor's Format button runs on). Run python3 scripts/format.py to
format everything or --check to verify without writing; equivalently,
from the image:
docker run --rm --user 0:0 -v "$PWD":/repo:rw \
ghcr.io/coderpuzzle/coderpuzzle:latest coderpuzzle format --check /repo/problems /repo/docs/FORMAT.mdWhen you add or edit a solution, run the formatter before pushing — the
format check is local (scripts/format.sh --check), not a CI gate.
python3 scripts/check.py --problems=all
python3 scripts/check.py --problems=0001_two-sum,0002_add-two-numbersThe static tier (bundle completeness, schema conformance, statement grammar,
duplicate ids/slugs, solution.* ⊇ starter.*, starter generator round-trip)
runs over the whole set by default; --bundles= restricts the per-bundle
checks to named keys, while the corpus-wide rules (misnamed directories,
duplicate ids/slugs) always scan everything — a new bundle can collide with
an untouched one. The runtime tier (executing each selected problem's
solutions against its cases through CoderPuzzle) runs only on the selected
problems. CI is incremental: each push verifies only the bundles and
solution files whose content hash has no recorded pass (changed, new, or
previously failed — see scripts/ci_state.py), and a sharded judge sweep
of every bundle runs on demand and weekly.
scripts/verify_solution.py remains the focused local gate for one bundle.