· Marc Morel · Engineering · 11 min read
Choosing Languages: Twelve years, many answers, one new question
From Scala to Kotlin to Go: twelve years of back-end language choices, the lessons behind each switch, and why AI may decide the next one.

Languages, in the plural with a new destination
I wrote languages, not language, on purpose. There is no single right answer to the question “which programming language should we use?” — and anyone who offers you one in a sentence has probably never had to live with the consequences.
The answer depends, first and foremost, on what you are building. Then it depends on everything around the code: the comfort of what your teams already know well, the depth of their expertise, the shifts of the market, and the very real pros and cons of hiring for one stack rather than another. A language is a technical choice, but it is also a human, economic and organisational one.
In my case, the plural was never a philosophical stance. It was a hard requirement. We built native mobile apps, each platform with its own language and toolchain. We built websites with rich, complex front-ends. We exposed APIs and processed large volumes of data. No single language could serve all of that well, and pretending otherwise would have been a costly illusion.
On the front-end, we chose Nuxt and Vue.js with TypeScript. TypeScript gave us what front-end code had long been missing: a typed language, backed by excellent linting, that catches mistakes before users do.
We deliberately did not choose React. Not for lack of qualities — but because, for a team like ours, it evolved far too fast. Even the most basic building blocks were moving targets. Take React Router: something as fundamental as navigating from one page to another saw its API reinvented across major versions. Every upgrade felt like a small earthquake. Patterns we had just mastered were deprecated; tutorials from six months earlier no longer applied; and explaining to the business that a sprint would go into re-learning how to route between pages was a conversation nobody wanted to have twice. We wanted to build products, not chase a framework.
For APIs and Big Data, Python was — and still is — a first-class citizen. Its data ecosystem, its libraries and its fluency among data engineers make it irreplaceable in that space.
This post, however, focuses on one part of that landscape: back-end languages. That is where my most significant decisions were made, where they had the deepest impact on teams, delivery and costs — and where the story has been the most eventful, from 2014 to today. The front-end, mobile and data choices above are the backdrop; the back-end is the journey.
In this post, I want to share a journey that spans several years: the language decisions I made as a technical leader, what worked, why it worked — and why, at certain moments, I had to change choices that were still working. Not because they had failed, but because the context around them had moved.
Act I (2014) — Scala: bringing talent back to a struggling JVM shop
My first major language decision as a technical leader came at a difficult time. I was leading a mainly JVM, mainly Java shop that badly needed renewal. Teams were stuck. Deliveries had become rare. And, as often happens when success disappears, pressure kept rising — until frictions between teams turned into genuinely toxic behaviours, and the mood reached its lowest point.
In that situation, I did not want to add technical upheaval to human upheaval. So I made a deliberate choice: keep the JVM. It was a well-known, battle-tested execution environment that our operations understood, with a vast library ecosystem we could keep relying on. What I wanted to change was not the runtime — it was who wanted to join us, and the energy they would bring.
My bet to attract the best talents was Scala, a JVM-compatible language that, at the time, carried a strong appeal among ambitious engineers. Its advantages were real:
- Expressiveness. A concise syntax, strong type inference and higher-order functions let us say a lot with little code.
- Functional programming as a first-class citizen. Immutability by default, pattern matching and case classes encouraged code that was easier to reason about and safer under concurrency.
- A powerful type system that could encode business rules and catch whole classes of errors at compile time.
- Seamless Java interoperability. We kept every Java library we depended on, and could migrate progressively rather than rewrite.
- A rich ecosystem for concurrent and data-intensive systems, with Akka, Play and Spark among its flagships.
- A talent magnet. Scala signalled engineering ambition, and it attracted people who cared deeply about their craft.
And it worked. In a shop of our size — around forty developers — a handful of very talented, very motivated people changes everything. Projects that had been stuck for months started to move again. We delivered business value quickly, and the codebase became cleaner along the way. More importantly, the mood began to shift: success breeds confidence, and confidence breeds collaboration. It was a first, genuine success.
The price of power
But Scala is hard. Hard to write well, and even harder to read — especially in a team where experts and beginners work side by side on the same codebase. The very flexibility that delighted our most senior engineers became a source of friction for everyone else.
Over time, the costs became impossible to ignore:
- Several languages in one. Scala can be written as “a better Java” or as pure functional programming with advanced libraries. Without strong discipline, a single codebase ends up containing several dialects.
- Implicits and abstraction depth. Implicit conversions and parameters, symbolic operators and advanced type-level constructs could make a few lines of code nearly opaque to anyone but their author.
- Slow compilation. Build times grew with the codebase, lengthening the feedback loop that developers rely on every minute of the day.
- Unfriendly tooling. The build tool had a steep learning curve of its own, and IDE support struggled to keep pace with the language’s more advanced features.
- Fragile upgrades. Binary incompatibility between major versions turned every upgrade into a coordinated effort across libraries.
- A narrower hiring pool. Seasoned Scala engineers were rare and expensive, and onboarding newcomers took a long time.
Ultimately, we were missing what I consider the hallmark of great engineering: doing complex things with simplicity. Instead, we were too often doing simple things with complexity. It was not as efficient as it should have been.
Interlude (2016) — Kotlin: the balanced middle ground
Before going further afield, we looked for a gentler path on familiar ground. That path was Kotlin, and for a while it was a genuinely good, balanced choice. It kept everything the JVM had given us, while removing much of what had made Scala hard to live with:
- Still the JVM. Same runtime, same operations, same libraries, and full interoperability with our existing Java and Scala code. No big-bang migration required.
- Modern, yet readable. Concise syntax, data classes, extension functions and functional constructs where they help — without the abstraction depth that made Scala code opaque to newcomers.
- Safety where it matters most. Null safety built into the type system removed one of the most common sources of production errors on the JVM.
- Pragmatic concurrency. Coroutines offered a readable way to write asynchronous code, without adopting a whole new programming paradigm.
- First-class tooling. Designed by the makers of IntelliJ, Kotlin came with excellent IDE support from day one, and compilation was noticeably faster than with Scala.
- A broader talent pool. Java developers became productive within weeks, and Kotlin’s status as the reference language for Android widened the pool of people who already knew it.
Kotlin restored a healthy balance between expressiveness and simplicity, and both our Java veterans and our Scala experts found their place in it. But it did not change one fundamental thing: we were still running on the JVM. As our move to the Google cloud Platform (GCP) accelerated, memory footprint, startup time and resource consumption were becoming first-order concerns. That is the context in which the next chapter began.
Act II (2017 – today) — Go: the quiet power of a boring language
The next turn came, as good ideas often do, from someone on the ground. A contractor in my team (Vincent Spiewak - Who wrote about the technical details here) came to me and said he wanted to build a proof of concept in Go.
The name was not new to me. Alumni from my previous companies, who were by then working at places like Algolia - and, of course, Google - had been telling me about it for a while. So I said yes.
The proof of concept was a great success. Go is a boring language, and that is precisely its strength. There is usually one obvious way to write something. Code written by a senior engineer looks very much like code written by a newcomer. A standard formatter ends style debates before they begin. Reading someone else’s code stops being an investigation.
The ecosystem was already mature and covered our entire stack: PostgreSQL, Elasticsearch, microservices and APIs. A solid standard library, fast compilation and single static binaries made development and deployment refreshingly straightforward.
What finally convinced me was performance and resource consumption. We were in the middle of a massive move to the cloud, where every megabyte of memory and every CPU cycle shows up on the invoice. Lightweight goroutines, a small memory footprint, fast startup and compact container images made Go an excellent fit for that new reality.
It was another great success. We brought in professional Go contractors with a few years of experience — very talented people, once again. And the Scala developers were happy too: they found a language that was fast to learn and delivered impressive performance. Adoption was fast, and largely enthusiastic. Since 2017, Go has remained our back-end language of choice — and it still is today.
Act III — 2026: a major change in two letters
Today, in 2026, a major change fits in two letters: AI.
After the first experiments, and with models and agent harnesses evolving at a remarkable pace, one thing has become clear to me: software development is no longer what it used to be. Agents now write, refactor and test a growing share of our code, and they are steadily moving from assistants to genuine contributors.
Looking back, the drivers behind my language choices have evolved with each chapter. First, they were human resources issues: turnover, attracting talent, hiring. Then they became FinOps and efficiency considerations: performance, resource consumption, the cost of running in the cloud.
Now, I believe language choices should — and maybe very soon must — be driven by a new factor: the preferences of agents.
The great inversion
In that context, some languages look very different from how they looked a few years ago. Rust is the clearest example. For humans, it has a famously steep learning curve: ownership, borrowing and lifetimes demand time and patience to master. For agents, that same strictness becomes an asset, which makes Rust a very compelling choice for backends:
- The compiler as a tireless reviewer. Rust’s precise, actionable error messages give an agent an immediate, reliable feedback loop. It iterates against the compiler until the code is correct.
- Whole classes of bugs eliminated by construction. Memory safety and data-race freedom are guaranteed at compile time, rather than left to the vigilance of a code review.
- Explicitness everywhere. Types, errors and ownership are all spelled out in the code, leaving very little implicit context for an agent to guess.
- Outstanding performance and frugality — which keeps the FinOps lessons of the previous chapter fully relevant.
This is where I see a kind of inversion. For humans, I chose languages that lowered cognitive load: simple, readable, quick to learn — the very reasons Go won. For agents, the learning curve matters far less. What matters is how much the language itself verifies, how explicit it is, and how quickly it tells you that you are wrong.
In other words, the languages I would choose for humans and the languages I would choose for agents are no longer necessarily the same.
What changes
Looking back, each choice was right for its time, and each one eventually had to give way to the next.
| Chapter | Language | Primary driver | What it gave us | Why we moved on |
|---|---|---|---|---|
| Act I (2014) | Scala (on the JVM) | People: turnover, attracting talent | Motivated experts, unblocked projects, a cleaner codebase | Too hard to write and read well in a mixed team; slow builds, difficult tooling |
| Interlude (2016) | Kotlin (on the JVM) | Balance: expressiveness without complexity | Readable code, null safety, great tooling, easier hiring | Still the JVM: footprint and resource costs in the cloud move |
| Act II (2017 – today) | Go | Efficiency: cloud costs, performance, simplicity | Fast adoption, readable code, a frugal footprint | Still our back-end language — but the context is shifting |
| Act III (2026) | Rust, among others | Agents: how well a language works for AI | A compiler that reviews every line, safety by construction | The story is still being written |
What has not changed is the principle I started with: languages, in the plural. A language choice is never purely technical. It reflects what you build, who builds it, what the market offers and what it costs — and, from now on, who or what writes the code.
The best choice is rarely the most fashionable one. It is the one that fits your context today, made with the humility to revisit it tomorrow.