Deep dive
Decision table
| Job | Pick | Why | Runner-up |
|---|---|---|---|
| Web/API backend, typical CRUD | Go, TypeScript (Node), Kotlin/Java, C# | Productive, mature HTTP/DB stacks, easy hiring | Python (Django/FastAPI) |
| Backend with hard latency/throughput targets | Rust | No GC pauses, predictable memory, async I/O | Go, C# |
| Database, storage engine, proxy, search engine | Rust or C++ | Control of layout, allocation, I/O | Zig, Go (if p99 GC pauses are acceptable) |
| Game engine, real-time audio, HFT | C++ | Ecosystem (Unreal, SDKs), toolchains, talent | Rust, Zig |
| Game logic on an engine | C# (Unity, Godot), C++/Blueprints (Unreal), GDScript | The engine decides | |
| Multiplayer game server | Rust, C#, Go, C++ | Tick loops need low jitter; share code with client if possible | Erlang/Elixir for lobbies/chat |
| ML training, research, data science | Python | PyTorch/JAX/NumPy; kernels underneath are C++/CUDA | Julia |
| Custom GPU kernels | CUDA C++ or Triton | Best tooling and libraries on NVIDIA | HIP (AMD), Metal (Apple) |
| Model inference serving | Python (vLLM, SGLang) or C++/Rust runtimes | Python orchestrates, C++/CUDA computes | Rust for the serving shell |
| CLI tools | Go or Rust | Single static binary, fast startup | Python with uv, Zig |
| Browser frontend | TypeScript | Only first-class option; WASM for hot loops | Rust → WASM for compute |
| Mobile apps | Swift (iOS), Kotlin (Android) | Platform APIs first-class | Dart/Flutter, React Native, KMP for shared code |
| Desktop apps | Platform native, or TypeScript + Electron/Tauri | Reuse web skills | C#/.NET (Windows), Qt (C++) |
| Chat, presence, millions of connections, telecom | Elixir/Erlang | Cheap processes, supervision, distribution | Go |
| Embedded, microcontrollers | C, Rust (embedded-hal, Embassy) | Vendor SDKs are C; Rust where HALs exist | Zig, C++ |
| OS kernels, drivers | C, Rust (Linux, Windows, Android drivers) | Kernel ABI and tooling | C++ (Windows), Zig |
| Data pipelines, analytics | SQL + Python | Let the engine (DuckDB, Postgres, Spark) do the work | Scala/Java on Spark |
| Glue, scripting, automation | Python, shell for < 50 lines | Fastest to write | TypeScript (Deno/Bun) |
| Enterprise systems, long-lived large codebases | Java/Kotlin, C# | Tooling, refactoring, stable runtimes | Go |
| Smart contracts | Solidity (EVM), Rust (Solana) | The chain decides |
Cost model
| Language | CPU (vs C) | Memory model | Concurrency | Startup | Build/deploy |
|---|---|---|---|---|---|
| Rust | ~1x | Ownership, no GC | Threads + async (Tokio) | ms | Static binary; slow compiles |
| C/C++ | 1x | Manual / RAII | Threads, coroutines (C++20) | ms | CMake + package pain |
| Zig | ~1x | Manual, explicit allocators | Threads | ms | Easy cross-compile; pre-1.0 churn |
| Go | ~1.5-3x | Concurrent GC | Goroutines (M:N) | ms | Static binary, very fast compiles |
| Java/Kotlin | ~1-2x after JIT warmup | Generational GC (G1, ZGC) | Threads, virtual threads, coroutines | 100s of ms (native-image: ms) | Fat JAR / container |
| C# | ~1-2x | Generational GC, value types | async/await, Tasks | 100s of ms (NativeAOT: ms) | Single-file or AOT binary |
| TypeScript/JS | ~2-5x | GC (V8) | Single-threaded event loop + workers | 10s of ms | Node/Bun/Deno, bundlers |
| Python | ~10-100x pure Python | Refcount + cycle GC | GIL (free-threaded build emerging), asyncio, processes | 10s of ms | uv/venv, containers |
| Erlang/Elixir | ~3-10x | Per-process heaps and GC | Millions of preemptive processes | 100s of ms | OTP releases |
Numbers are order-of-magnitude for CPU-bound code; I/O-bound services see much smaller gaps.
How to decide
- List the hardest technical requirement (p99 < 1 ms? GPU kernels? 1M websockets? iOS?). It usually eliminates most options.
- Check the ecosystem: is there a mature library for your database, protocol, cloud SDK, auth provider?
- Weigh the team: a language nobody on the team knows costs months; hiring markets differ by region.
- Prototype the hot path and measure before committing to a "fast" language for everything.
- Plan the boundary for mixing: FFI (C ABI), WASM, gRPC between services, or a GPU language under Python.
Code
A typical split: Python orchestration calling a Rust hot loop through PyO3/maturin.
// Cargo.toml: [lib] name = "fastlib", crate-type = ["cdylib"]; pyo3 = { version = "0.22", features = ["extension-module"] }
use pyo3::prelude::*;
/// Count positions where two equal-length byte strings differ (hot loop in Rust).
#[pyfunction]
fn hamming(a: &[u8], b: &[u8]) -> PyResult<usize> {
if a.len() != b.len() {
return Err(pyo3::exceptions::PyValueError::new_err("length mismatch"));
}
Ok(a.iter().zip(b).filter(|(x, y)| x != y).count())
}
#[pymodule]
fn fastlib(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(hamming, m)?)?;
Ok(())
}
uv tool run maturin develop --release # builds and installs into the active venv
python -c "import fastlib; print(fastlib.hamming(b'abc', b'abd'))"
Efficiency
- Cost: per-language costs are on each language page; the ones that decide a choice, with their sources: a goroutine starts with a few KB of stack (Go FAQ), an Erlang process uses 327 words, about 2.6 KB (Erlang Efficiency Guide), a CPython
intis 28 bytes (measured withsys.getsizeof), Spring PetClinic starts in 4.5 s on JDK 23 and 2.6 s with an AOT cache on JDK 24 (JEP 483), and Wasm ran SPEC CPU 45-55 % slower than native in browsers (Jangda et al., 2019). Bound: usually the database and network, not the language. - Levers:
- profile, then move only the hot function to a faster language — one loop is usually the problem (order of) — Python or JS services with one hot spot
- batch calls across the language boundary — one call per buffer, not per element (no figure) — PyO3, N-API, Wasm and cgo boundaries
- ahead-of-time compile for startup (JVM AOT cache, Native AOT, Wasm precompile) — 42 % lower startup on PetClinic (JEP 483) — serverless and CLIs
- tune the GC instead of leaving it (
GOGC, ZGC,MaxRAMPercentage) — halve GC CPU or cut pauses (Go GC guide, JEP 439) — GC'd services with a latency or memory target - keep to two to four languages per organization — fewer toolchains to secure and staff (order of) — whole-org decisions
- Naive → standard → state of the art:
- rewrite everything in the fastest language · months of work, gain only if CPU-bound
- profile, then fix the query or loop in the current language · usually enough
- current language plus a native hot path behind a stable ABI (PyO3, napi-rs, UniFFI) · native speed where it matters
Pitfalls
- Choosing by benchmark-game numbers for an I/O-bound CRUD service.
- Choosing by hype for a team that can't support it in production (on-call, debugging, profiling).
- Rewriting a working system for speed without profiling; usually one query or one loop is the problem.
- Too many languages in one org: each adds CI, security scanning, libraries and hiring overhead. Two to four is plenty.
- Ignoring deployment: GC'd runtimes in small containers, JIT warmup in serverless, Python native wheels on unusual platforms.
- Using dynamic languages for large codebases without type checking (mypy/pyright, TypeScript strict).
Implementations
| name | lang | repo | install | verdict | licence | health | as_of | notes |
|---|---|---|---|---|---|---|---|---|
| PyO3 | Rust | PyO3/pyo3 | cargo add pyo3 | — | — | — | — | Rust bindings for Python |
| maturin | Rust | PyO3/maturin | pip install maturin | — | — | — | — | build and publish PyO3 wheels |
| napi-rs | Rust | napi-rs/napi-rs | cargo add napi | — | — | — | — | Rust addons for Node.js |
| UniFFI | Rust | mozilla/uniffi-rs | cargo add uniffi | — | — | — | — | one Rust core for Kotlin, Swift and Python |
| rust-bindgen | Rust | rust-lang/rust-bindgen | cargo add bindgen --build | — | — | — | — | generate Rust bindings from C headers |
Sources
- Gouy, The Computer Language Benchmarks Game. site
- Pereira et al., Energy Efficiency across Programming Languages, SLE 2017. paper
- TechEmpower, Web Framework Benchmarks. site
- Per-language sources are linked on Go, Rust, Python, Java/Kotlin, C#, Erlang/Elixir and WebAssembly.