ADR-010: Two-language ceiling — TypeScript and Go only
- Status: Accepted
- Date: 2026-05-23
- Deciders: CTO / Founding Engineering
Context
Polyglot engineering teams pay a real ongoing tax: build pipelines, security scanning, dependency tracking, deployment tooling, on-call expertise, and hiring all multiply by the number of languages. Past some point that tax exceeds the per-domain win from "the right tool for the job."
DemozPay's surface area is large but the technical demands are not exotic — payroll-linked fintech in TS (NestJS + Next.js) and Go (ledger + integration gateway + workers) covers everything we need.
Decision
- TypeScript is used for: the API monolith (
apps/api, NestJS), every frontend (apps/*-web, Next.js), shared and domain packages (packages/). - Go is used for: independently deployable services (
services/*) — money truth (ledger), external integrations (integration-gateway), high-throughput workers (notifications). - No other language is introduced for new code without a successor ADR amending this one.
- Existing third-party tools, SQL migrations, shell scripts, Dockerfiles, Terraform, and Helm charts are not "languages" for this purpose.
Alternatives considered
- Rust for the ledger. Strong language fit (memory safety, performance), but: smaller talent pool in Addis, longer build pipelines, ecosystem gaps for the database drivers we use, and the marginal gain over Go is small for our workload. Not worth a third language.
- Java/Kotlin anywhere. Rejected — runtime weight, slow startup, ecosystem complexity, JVM ops overhead.
- Python for any service surface. Rejected — packaging story, GIL, type system. Acceptable for one-off data scripts in
tooling/scripts/only.
Consequences
Positive
- One TS toolchain (pnpm + Nx + tsc + jest + eslint), one Go toolchain (go test, go vet, golangci-lint, buf).
- One hiring profile for backend engineers ("TS + some Go" or "Go + some TS") instead of three.
- Cross-team review works — every reviewer can read both stacks.
Negative
- "But this would be 30% faster in Rust" arguments will come up. The reply is: the ledger isn't the bottleneck, and 30% on a non-bottleneck is not worth a language.
- We forgo some ecosystem advantages (e.g. Python's data tooling, Rust's WASM story). Acceptable.
Follow-ups
tooling/eslint/and Go module setup encode the two toolchains.- Any ADR proposing a new service language must explicitly supersede this ADR and provide the cost analysis.
- Internal tooling (one-off data scripts, ops automation) may use bash or Python freely; those live under
tooling/scripts/and are not part of the production deploy surface.