Around 2020 I made a deliberate decision to stop spreading learning effort across Python, Ruby, and PHP and consolidate around TypeScript. This is the reasoning behind that shift — and why it led me toward statically typed compiled languages rather than away from them.
The Context Switch Cost
For several years I was maintaining professional fluency across multiple languages simultaneously. Python for data pipelines and NLP work, Ruby on Rails for server-side applications, PHP surfacing in legacy projects, and JavaScript on the frontend. Each language has its own idioms, standard library conventions, ecosystem tooling, testing patterns, and community norms.
The cognitive overhead of switching between them within a single working day is real and underappreciated. It is not just syntax — it is the mental model each language carries. Ruby is optimistic and expressive, designed to make the programmer happy. Python is pragmatic and readable, oriented around explicitness. PHP carries the weight of its history. Each time you context switch you are not just recalling different keywords; you are resetting to a different way of thinking about problems.
I hit a clear inflection point working on a Rails backend with a React TypeScript frontend. Both were on the same product, often solving mirror-image versions of the same problem — validating data, shaping API responses, handling errors. The backend expressed everything in Ruby’s fluid, duck-typed idioms. The frontend expressed everything in TypeScript’s structural type system. Two coherent, internally consistent languages — and a persistent translation overhead sitting between them.
Why TypeScript Specifically
I had used Node.js for server-side work previously and wasn’t convinced. The ecosystem in the mid-2010s was fragmented — callback hell gave way to promises gave way to async/await, npm was chaotic, and the lack of types meant that large server-side codebases became genuinely hard to reason about. The dynamic, untyped nature of JavaScript that made it fast to prototype was the same property that made it expensive to maintain.
TypeScript changed the calculus. What convinced me wasn’t the syntax — it was the ability to describe behaviour through types, not just data shapes.
A TypeScript interface isn’t just documentation. It is a constraint the compiler enforces. When you model a domain concept as a type, the type system tells you when you’ve violated the model — before runtime, before tests, before code review. You can express things like: “this function returns either a result or an error, and the caller must handle both.” You can model state machines where invalid transitions are compile-time errors. You can use discriminated unions to make illegal states unrepresentable.
type LoadingState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Dataset }
| { status: 'error'; message: string };
function render(state: LoadingState) {
switch (state.status) {
case 'success': return renderData(state.data); // data is typed here
case 'error': return renderError(state.message); // message is typed here
}
}
This kind of precision was not achievable in Python, Ruby, or PHP without external tooling that sat awkwardly on top of the language. In TypeScript it is idiomatic.
What I Gave Up — and What I Didn’t Miss
Python’s scientific and NLP ecosystem is genuinely unmatched. NumPy, pandas, NLTK, spaCy, the entire machine learning stack — none of this exists in the JavaScript world at remotely the same maturity. I knew consolidating around TypeScript meant working at the boundary with Python tooling rather than within it.
That trade-off was acceptable because my primary interest in NLP and information retrieval is in the systems that serve it — the search indices, the pipelines, the interfaces — not in the training and experimentation layer where Python is dominant. TypeScript on the server via Node.js or Deno covers the former; Python or Rust handle the latter when necessary.
Ruby I missed less than expected. Rails is a remarkable piece of engineering and convention-over-configuration is genuinely productive for certain problem shapes. But the magic that makes Rails fast to prototype makes large Rails applications difficult to navigate. When you are working across a full codebase the implicit conventions become friction. TypeScript’s explicitness is a better fit for how I want to work at scale.
PHP I did not miss.
Where This Led
Consolidating on TypeScript did not mean staying within the JavaScript ecosystem. It meant I had identified what I was looking for in a language: static types, explicit constraints, a type system expressive enough to model domain invariants, and ideally a single language that could operate across the stack.
TypeScript gets you surprisingly far on those criteria. But it has limits — it erases to JavaScript at runtime, the type system has known unsoundness, and the performance ceiling is JavaScript’s performance ceiling.
Those limits pointed naturally toward compiled, statically typed languages. Nim was the first one that captured my attention — statically typed, compiled, with Python-like syntax and optional garbage collection. It felt like the direction TypeScript was pointing toward but hadn’t arrived at yet.
From Nim the path went to Go — a language where the simplicity is a design principle, compilation is near-instant, and the standard library is the right answer for most problems. And from there, inevitably, to Rust — which I will cover separately, because the case for Rust deserves its own treatment.
The short version: once you have internalised what a strong type system gives you, the appetite for runtime errors you could have prevented at compile time disappears. The direction of travel has been consistent since 2020 — not away from types, but toward stricter ones.