Skip to main content

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.