Benchmarking different FizzBuzz implementations in JavaScript (and Rust via napi-rs) to compare performance characteristics of various optimization strategies.
| # | Implementation | Description |
|---|---|---|
| 1 | Naive | Classic approach using string concatenation |
| 2 | Least Common Multiple | Uses modulo checks for 3, 5, and 15 |
| 3 | Optimized Modulo | Nested conditionals to minimize modulo operations |
| 4 | Array Pre-allocation | Pre-allocated array with optimized modulo checks |
| 5 | Loop Unrolling | Loop unrolling exploiting the 15-iteration FizzBuzz cycle |
| 6 | Tail Recursion + Trampolining | Avoids stack overflow with trampoline pattern |
| 7 | Domain-Specific Language | Composable chain of divisibility checks using continuation passing |
| 8 | Rust napi-rs | Native Rust addon returning a JS array via N-API |
| 9 | Rust napi-rs (JSON.parse) | Native Rust addon returning JSON, parsed on the JS side |
- Node.js 20 or later
- Rust toolchain (for building the napi-rs addon)
Install dependencies:
npm installBuild the Rust napi addon:
npm run build:rustRun the benchmark:
npm run benchmarkRun the JavaScript tests:
npm testRun the Rust tests:
npm run test:rustYou can profile individual JavaScript FizzBuzz implementations using V8's optimization tracing flags. This is useful for inspecting which functions get optimized or deoptimized by the V8 JIT compiler.
npm run profile:opt <functionName>Available functions: fizzBuzzNaive, fizzBuzzLCM, fizzBuzzModulo, fizzBuzzPreallocated, fizzBuzzUnrolled, fizzBuzzRecursive, fizzBuzzDSL.
The script runs the selected function 100,000 times with N=10,000 while --trace-opt and --trace-deopt print V8's optimization and deoptimization decisions to stderr.
This project includes a GitHub Actions workflow that runs benchmarks across multiple runtimes:
- Node.js — versions 20, 22, 24, 25
- Bun — latest
- Deno — latest
Results are collected and displayed in the Actions summary page.