Plate 21
c8 vs nyc: Should You Switch? 4.2s vs 7.4s
c8 12.0.0 vs nyc 18.0.0 on 80 CommonJS modules / 480 mocha tests: coverage-run median 4222 ms vs 7415 ms (~1.76x; no-coverage baseline 1734 ms). Same code, different totals: branches 81.3% (c8) vs 47.9% (nyc).
Aditya Challa5 min read
On 80 CommonJS modules with 480 mocha tests, c8 12.0 finished a full coverage run in a median 4.2 s and nyc 18.0 took 7.4 s (~1.76x slower), against 1.7 s with no coverage at all. The two tools also disagreed on the numbers: c8 reported 81.3% branches and 63.3% lines, nyc reported 47.9% branches and 58.5% lines for the same run.
Short answer: switch to c8 if your tests run on plain Node and you want lower coverage overhead with no instrumentation step. Stay on nyc if you rely on Babel-instrumented code, Istanbul-specific ignore hints, or coverage thresholds you cannot re-baseline this sprint. No affiliate links in this post.
What I tested
- Machine: 8 vCPU Intel Xeon / 15 GB RAM Linux cloud box, Node 20.19.2, tested 5 Oct 2026 (about 23:13 to 23:15 IST). Shared box; variants interleaved each round.
- Fixture: 80 CommonJS files under
src/(7,280 lines, 8 functions each with if/else-if/else loops, plus one never-called function per file) and 80 mocha test files (480 tests). Generator script kept with the raw results. - Tools:
c812.0.0,nyc18.0.0,mocha12.0.3. Same config for both: include every.jsfile undersrc/,all: true, reporterstext-summaryandlcov. - Timing: one nyc warmup to fill its cache, then five interleaved rounds of four variants: plain
mocha,c8 mocha,nyc mocha(default cache on),nyc --cache=false mocha. Coverage folders deleted before every run. Wall clock viaprocess.hrtimearound the fullnpxcommand. - Findings: medians 1733.9 ms (no coverage), 4221.8 ms (c8), 7414.5 ms (nyc, cache on), 8677.9 ms (nyc, cache off). c8 runs 4135 to 4359 ms; nyc cache-on runs 7323 to 8167 ms.
- One surprise: the coverage percentages are not comparable. c8 counted 7,280 lines and 3,251 branches; nyc counted 5,280 lines and 5,280 branches on identical code. Any CI threshold tuned on nyc will move when you switch.
- Not tested: TypeScript or ESM sources, Babel-instrumented builds, Jest or Vitest coverage providers, the
--experimental-monocartflag, Windows/macOS, suites above a few thousand tests.
Is c8 faster than nyc?
| Variant | Median wall | Runs (ms) | Overhead vs no coverage |
|---|---|---|---|
| mocha, no coverage | 1734 ms | 1734 / 1665 / 1665 / 1816 / 1759 | none |
| c8 12.0 | 4222 ms | 4359 / 4177 / 4135 / 4222 / 4340 | +2488 ms (2.4x) |
| nyc 18.0, cache on | 7415 ms | 7997 / 7415 / 8167 / 7323 / 7386 | +5681 ms (4.3x) |
| nyc 18.0, cache off | 8678 ms | 8678 / 9065 / 8562 / 8766 / 8452 | +6944 ms (5.0x) |
Yes, on this fixture. c8 reads coverage that V8 already collects while the code runs, so there is no source rewrite before tests start. nyc rewrites every included file with Istanbul counters first, and the counters also slow down hot loops like the ones in this fixture. Its cache only removed about 1.3 s of the cost.
Test-runner choice is a separate decision. The Jest vs Vitest post and the Vitest 5 upgrade post cover the runner itself; this post only covers coverage for a mocha or plain Node suite. If you also need a DOM, the jsdom vs happy-dom post has those numbers.
Why do c8 and nyc report different coverage?
| Metric | c8 12.0 | nyc 18.0 |
|---|---|---|
| Statements | 63.31% (4609/7280) | 57.97% (5009/8640) |
| Branches | 81.26% (2642/3251) | 47.89% (2529/5280) |
| Functions | 66.66% (480/720) | 66.66% (480/720) |
| Lines | 63.31% (4609/7280) | 58.50% (3089/5280) |
c8 converts V8 byte ranges to lines, so every physical line counts, including closing braces. nyc counts the statements and branch paths Istanbul inserted at instrumentation time. Function coverage matched exactly at 480 of 720. Branch coverage moved the most, by 33 points. Plan to reset your thresholds the day you switch, and compare per-file reports before trusting the new totals.
Disk use also differed. c8 left 3.9 MB in coverage/ (the lcov report plus raw V8 JSON in coverage/tmp). nyc left 1.6 MB in coverage/ plus 2.8 MB in .nyc_output/. Both lcov.info files were about 150 to 160 KB.
Should you switch from nyc to c8?
| Situation | My pick |
|---|---|
| Plain Node or mocha suite, CI time matters | Switch to c8; re-measure on your suite |
| Coverage gate in CI tuned to nyc numbers | Switch only with a planned threshold reset |
Code is instrumented through Babel (babel-plugin-istanbul) | Stay on nyc |
You depend on /* istanbul ignore */ hints everywhere | Test c8 first; it supports c8 ignore hints instead |
| Tests already run in Vitest or Jest | Use that runner's coverage provider, not either CLI |
Bottom line: on 480 mocha tests, c8 added about 2.5 s of coverage cost and nyc added about 5.7 s, so c8 was roughly 1.76x faster end to end. Who should not switch yet: teams whose release gate depends on today's nyc percentages and who cannot re-baseline. For a Node runtime choice on the same box, see the tsx vs ts-node post.
How this was made: I generated the fixture, ran the interleaved coverage runs on the ShopperCove test box, and kept the timing JSON; the write-up was drafted with AI help and checked against that output.
Sources
- https://github.com/bcoe/c8
- https://github.com/istanbuljs/nyc
- https://nodejs.org/api/cli.html#node_v8_coveragedir
- https://www.npmjs.com/package/c8
- https://www.npmjs.com/package/nyc
- https://mochajs.org/
Related
- https://www.shoppercove.com/blog/jest-vs-vitest
- https://www.shoppercove.com/blog/vitest-5-should-you-upgrade-october-2026
- https://www.shoppercove.com/blog/jsdom-vs-happy-dom-vitest
- https://www.shoppercove.com/blog/tsx-vs-ts-node
- https://www.shoppercove.com/blog/playwright-1-63-test-locks-october-2026
- https://www.shoppercove.com/blog/knip-vs-depcheck
- https://www.shoppercove.com/blog/oxlint-vs-eslint
- https://www.shoppercove.com/blog/nodejs-26-lts-october-2026-schedule-change
Lab evidence
What I found running this
Hands-on on ShopperCove box 5 Oct 2026 ~23:13-23:15 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). Fixture: 80 CJS modules in src/ (7,280 lines, 8 branchy functions + 1 unused each), 80 mocha test files / 480 tests. Versions: c8 12.0.0, nyc 18.0.0, mocha 12.0.3; same include src/*.js, all:true, reporters text-summary+lcov. 1 nyc cache warmup, then 5 interleaved rounds, coverage dirs deleted each run. Medians: no coverage 1733.9 ms (1733.9/1664.6/1664.8/1816/1758.9); c8 4221.8 ms (4359.2/4176.7/4135.4/4221.8/4340.1); nyc cache-on 7414.5 ms (7997/7414.5/8167.1/7323.3/7385.9); nyc --cache=false 8677.9 ms (8677.9/9065/8562/8765.5/8451.5). Coverage: c8 stmts 63.31% (4609/7280), branches 81.26% (2642/3251), funcs 66.66%, lines 63.31%; nyc stmts 57.97% (5009/8640), branches 47.89% (2529/5280), funcs 66.66%, lines 58.5% (3089/5280). Disk: c8 coverage/ 3.9 MB (incl. tmp V8 JSON); nyc coverage/ 1.6 MB + .nyc_output 2.8 MB. Raw: /workspace/bench-c8-nyc/res-c8-nyc.json. Not tested: TS/ESM sources, Babel instrumentation, Jest/Vitest providers, --experimental-monocart, Windows/macOS. No affiliate.
Related links
Plate 68
pnpm vs npm vs Bun: Which Installs Fastest? 14s, 3.1s, 1.5s
npm 12.2.0 vs pnpm 12.9.1 vs Bun 1.4.2 on one 24-dependency React + Vite app: from scratch 14.2 s / 3.1 s / 1.5 s; lockfile + warm cache 2.9 s / 0.28 s / 0.27 s; lockfile + empty cache 3.9 s / 2.3 s / 0.45 s. Disk, lockfile size and the pnpm phantom-dependency catch.
5 Oct 2026
Plate 04
Ajv vs Zod: 3.9M vs 1.1M Validations per Second
ajv 8.20.0 (+ajv-formats 3.0.1) vs zod 4.6.5 on the same nested user shape as zod-vs-valibot. Warm medians (2k warmup + 9x50k): Ajv validate ok 3,933,337 ops/s vs zod.safeParse 1,138,406 (~3.5x). Bad input: Ajv allErrors 4,094,452 vs Zod 435,537 (~9.4x). Setup: Ajv new+addFormats+compile median 6.47 ms vs Zod build 0.17 ms; break-even ~10k validations. Browser gzip: Ajv runtime 41 KB, Ajv standalone 1.8 KB, Zod classic 93 KB, zod/mini 6.8 KB. Fixture /workspace/bench-ajv-zod/.
5 Oct 2026
Plate 12
Yarn Berry vs pnpm: 1.3s vs 0.24s Install
Yarn 4.9.2 (Berry PnP + node-modules linker) vs pnpm 12.9.1 on one 22-dependency React+Vite app. Isolated caches (Yarn enableGlobalCache false; pnpm --store-dir). Warm medians (5): Yarn PnP 1.307 s, Yarn nm 2.471 s, pnpm 0.240 s. Lock+empty medians 1.656 / 2.789 / 0.725 s. Clean disk: Yarn PnP 246 MB; Yarn nm modules 189 MB; pnpm hardlink project 167 MB. Fixture /workspace/bench-yarn-pnpm/. Distinct from pnpm-vs-npm-vs-bun (no Yarn).
5 Oct 2026