Benchmarks
These results measure one synthetic workload with overlapping linter file patterns. They illustrate how orchestration affects this setup; they are not a prediction for every repository or a survey of every tool’s available configuration.
The recorded run was generated on 2026-03-23. No new benchmark run is implied by this page’s last-updated date.
Recorded results
Mean wall time, in seconds. Lower is faster.
| Tool | All files (6158) | Staged changes (50) |
|---|---|---|
| hk | 3.15 s | 0.42 s |
| lefthook | 20.83 s | 0.72 s |
| pre-commit | 10.60 s | 0.81 s |
| prek | 10.52 s | 0.74 s |

Download the recorded data, including standard deviations, minimums, and maximums.
Workload
The generator defaults to roughly 6,000 files: 4,000 Python, 500 JavaScript/TypeScript, 500 JSON, 500 shell, 250 YAML, 200 CSS, and 200 Markdown files, plus project configuration.
Ten configured steps include ESLint, Prettier, Black, Ruff linting, Ruff formatting, jq, yq, shfmt, trailing whitespace, and final newlines. The whitespace steps overlap with the language-specific steps.
The committed runner:
- Invokes hk’s pre-commit hook in fix mode, with stashing disabled through
HK_STASH=false. - Configures lefthook with sequential execution to avoid concurrent writes from overlapping formatters in this workload.
- Runs pre-commit and prek using the provided hook definitions.
- Resets the fixture between runs, primes hk’s configuration cache, and uses Hyperfine warmups and repeated measurements.
The runner and tool configurations define the comparison. These choices matter as much as the timing values.
Limitations
This workload favors concurrent work across languages while also exercising overlapping formatters. A small project, a single linter, or tools that already parallelize internally may see different results.
Stashing is disabled in the runner, so the results do not measure partial-commit restoration. Hyperfine is configured to tolerate nonzero exits from lint commands; timings alone do not establish equivalent fixes or successful checks.
The recorded JSON does not include machine specifications or exact tool versions. Treat it as a historical example and rerun the workload with those details recorded before using the numbers for a tool-selection decision. Current scripts may also differ from the ones used for the recorded result.
Reproduce
Use a disposable directory. The benchmark runner resets its fixture repository and overwrites generated results.
Install hk, hyperfine, lefthook, pre-commit, prek, prettier, eslint, black, ruff, shfmt, jq, yq, and uv, and record their versions. The shell scripts expect a Unix-like environment and compatible command-line utilities.
From the repository root:
benchmark/generate-project.sh /tmp/hk-bench
benchmark/run.sh /tmp/hk-benchTo change the workload or number of repetitions:
NUM_JS=500 NUM_PY=500 benchmark/generate-project.sh /tmp/hk-bench
RUNS=20 WARMUP=3 benchmark/run.sh /tmp/hk-benchResults are written to benchmark/results/; the runner also updates docs/public/benchmark.png and docs/public/benchmark-data.json.
For your own project, start with hk timing reports. See Why hk? for the execution model.
