Programming

The Stability Tax: Why Elixir's Boring Versioning Is a Better AI Target

A decade of semantic versioning stability means AI agents trained on 2020 code still produce working 2026 code

13 min read elixir, ai, versioning, ecosystem, developer-experience

Last week, I asked Claude to scaffold a GenServer for a rate-limiting service. The code it generated used patterns from a 2020 blog post. Every line compiled; every function worked.

Try that with React.

The AI generated a class component. I needed a functional component with hooks.react-hooks-migration The training data was three years old; the code was unusable. I spent twenty minutes rewriting what should have been a copy-paste job.

This keeps happening. The ecosystems these models learned from keep moving, and the weights can't keep up. JavaScript frameworks deprecate patterns faster than models can retrain; Python libraries break backward compatibility between minor versions. The code that taught these systems how to program is slowly rotting in their weights.

Elixir doesn't have this problem, and that's by design.

The Hidden Cost of Churn

Every breaking change in a language or framework creates a tax. It falls on the developers who migrate, and on every system that learned the old way.

React hooks landed in February 2019. Before that, class components were the canonical pattern; every tutorial, every Stack Overflow answer, every code example used them. After hooks, class components became legacy --- still supported, but not what anyone would write anymore.

GPT-3 trained on data through 2019. GPT-4's cutoff was 2021. Claude's knowledge extends further, but the corpus includes years of pre-hooks React code. When you ask an AI to write a React component, you're rolling dice on which era's patterns it reaches for.

Create React App made this worse.cra-deprecation For years, it was the official way to bootstrap a React project. Then in 2023, the React team deprecated it. The replacement? Pick from Vite, Next.js, Remix, or whatever the community decides is canonical this quarter. Every tutorial written before 2023 now points to a dead end.

Python's 2-to-3 transition took a decade.python-print From 2008 to 2020, the ecosystem maintained two parallel versions; library authors had to support both. Tutorials had to specify which version they targeted. The training data from this period is a minefield --- code that looks syntactically valid might fail on print statements alone.

These are the mainstream experience of modern software development. Ecosystems optimize for new features, new paradigms, new ways of doing things; the cost is measured in migration guides and deprecation warnings.

For humans, this is manageable --- annoying, but manageable. For AI systems trained on historical code, the decay is structural: a model can't read a migration guide, and the outdated patterns sit in its weights until the next training run.

Elixir's Boring History

Elixir 1.0 released in September 2014. Jose Valim and the core team made a decision that seemed conservative at the time --- semantic versioning, with a real commitment to backward compatibility within major versions.semver-commitment

They haven't released 2.0.

As of early 2026, we're on Elixir 1.18. Twelve years, eighteen minor versions, zero breaking changes to the core language. Code written for 1.0 still compiles on 1.18. The Enum module works the same way; GenServer callbacks haven't changed. Pattern matching syntax is identical.

Phoenix followed the same philosophy. The framework has maintained its 1.x lineage for years, adding features like LiveView and Streams without breaking existing applications.liveview-launch A Phoenix app from 2018 runs on Phoenix 1.7 with minimal changes --- mostly configuration updates, not code rewrites.

Ecto tells the same story. Version 3.0 released in 2018; we're still on Ecto 3.x. The query syntax, the changeset patterns, the repo abstraction; all stable. A developer who learned Ecto from a 2019 tutorial can apply that knowledge directly to a 2026 project.

This isn't stagnation. Elixir added the with construct,elixir-with improved compiler warnings, introduced dbg/2 for debugging,dbg-macro and shipped a dozen quality-of-life improvements. Phoenix gained LiveView --- real-time capabilities that rival frontend frameworks. Ecto improved performance and added features.

But they did it additively, layering new capabilities on top without disturbing the old patterns.

The BEAM virtual machine underneath all of this has been stable for over thirty years.beam-telecom OTP behaviors designed in the 1990s still work; Erlang's process model hasn't changed. The fundamentals are proven, tested, and not going anywhere.

Boring? Absolutely.

What AI Training Data Looks Like

The training data has a long tail.

When you train a model on code, you feed it far more than the latest GitHub commits. You're ingesting years of Stack Overflow answers, archived tutorials, blog posts from 2018, open-source projects that haven't been updated since their last release. The corpus is historical by nature.

A model trained in 2024 might include code from:

  • 2024 GitHub repos (recent, current patterns)
  • 2022-2023 blog posts and tutorials
  • Stack Overflow answers from 2019-2023
  • Open-source libraries with commits spanning a decade
  • Documentation pages cached at various points in time

The distribution isn't uniform. Popular questions from 2020 might have more representation than obscure patterns from 2024; the model learns from whatever generated the most text, not whatever represents best practices today.training-distribution

For languages with rapid churn, this creates a version lottery. The AI might generate code using patterns that were standard three years ago, deprecated two years ago, and actively harmful today. You can prompt it to use current patterns, but you're fighting against the weight of historical examples.

In Elixir, that same long tail is an asset.

That 2020 blog post about GenServer patterns? Still valid. The 2019 Phoenix tutorial? The routing and controller patterns work. The 2021 Stack Overflow answer about Ecto queries? Copy-paste ready.

The stability compounds. Every year that Elixir and Phoenix hold the line on backward compatibility, the training data gets better. Old examples reinforce current patterns.

I've been using Claude for Elixir development for months, and the code it generates doesn't feel dated; when it pulls from older examples, those examples still work. I'm not constantly correcting deprecated function calls or outdated module structures.

That comes down to a deliberate choice: the ecosystem decided stability was worth preserving, and kept deciding it for a decade.

Fast Compilation as Iterative Debugging

AI coding assistants work best in tight feedback loops. Generate code, run it, see errors, fix them, repeat.

Elixir compiles fast. A typical Phoenix project recompiles changed modules in under a second; the development server hot-reloads without restarting. You can go from "the AI wrote something" to "I know if it works" in seconds.

When an AI generates code with subtle errors --- a wrong function arity, a missing pattern match, a type mismatch --- fast compilation surfaces the problem immediately. The error messages are clear; the fix is usually obvious. The iteration cycle is measured in seconds.

Compare this to a webpack build that takes thirty seconds. Or a TypeScript compilation that crawls through node_modules. Or a Python import that fails at runtime because a dependency version doesn't match.

Slow feedback loops kill the AI advantage. If you have to wait a minute between attempts, you start doing more work mentally before trying anything; the cost of experimentation goes up. You become more conservative, more careful, more likely to write the code yourself.

Fast compilation inverts this. You can let the AI try things. Generate three implementations, compile all of them, see which one the compiler likes. Treat the AI as a fast first-draft generator and the compiler as the filter.

Elixir's compiler is also unusually good at explaining what went wrong. "undefined function" means what it says; pattern match failures point to the exact clause. The errors are actionable.

This creates a workflow where AI generation and human oversight fit together without friction. The AI writes, the compiler catches the obvious mistakes, and you fix the subtle ones. Each step is fast enough that the whole process feels interactive.

Operational Simplicity

Fewer things to configure wrong.

A typical React project in 2026 requires decisions about build tools (Vite? Turbopack? Rspack?), meta-frameworks (Next.js? Remix? Astro?), state management (Redux? Zustand? Jotai?), styling (Tailwind? CSS modules? styled-components?), and testing (Jest? Vitest? Playwright?). Each choice has implications; each tool has its own configuration format, its own quirks, its own version compatibility matrix. The AI has to know React, and your specific combination of tools, configured your specific way.

Phoenix's answer is simpler: it's just Phoenix.

The asset pipeline is built in. LiveView handles interactivity. Ecto handles the database. Tailwind is pre-configured if you want it. Testing uses ExUnit, deployment uses releases. You don't pick a bundler because Phoenix's asset system handles it. You don't choose a state management library because LiveView's assigns and streams cover most cases.

When you ask an AI to help with a Phoenix project, there's less context to provide. "It's a Phoenix app" communicates more than "It's a React app" because Phoenix apps are more similar to each other than React apps are.

That sameness is the point --- the AI can make reasonable assumptions, and the code it generates is more likely to slot into your project without adjustment. Less impedance mismatch between what the model learned and what your project needs.

I've watched AI assistants struggle with JavaScript projects because every project is a unique snowflake of build tool configurations and dependency choices. The model has to guess which variant you're using; it often guesses wrong.

Phoenix projects are boringly similar. That's a feature.

The Ecosystem Trade-off

Elixir's ecosystem is smaller than JavaScript's or Python's. Fewer packages, fewer tutorials, fewer Stack Overflow answers. This is true, and it matters.

If you need a library for an obscure use case, JavaScript probably has three options and Elixir has one or none. If you want to hire developers, the JavaScript talent pool is orders of magnitude larger. If you're looking for tutorials, JavaScript has more noise but also more signal.

The smaller training corpus means AI models have seen less Elixir code overall; they handle common patterns well but struggle with edge cases. The depth of knowledge is shallower.

The Elixir code in the training data is more likely to be good. Smaller community means fewer low-quality tutorials, fewer copy-paste answers from developers who don't understand what they're copying; the ratio of signal to noise is higher.small-community-filter

I've noticed this in practice. When AI generates Elixir code, it tends to use idiomatic patterns. The OTP conventions are correct; the function signatures follow community norms. It's as if the training data was filtered for quality by the community's smaller size.

Whether this trade-off works depends on your project. For many applications, Phoenix covers 90% of what you need, and that 90% works well with AI assistance. The remaining 10% where you're on your own might be worth the stability you gain everywhere else.

Betting on Boring

The languages and frameworks that benefit most from AI assistance aren't the ones with the most features or the most active development. They're the ones with the most stable APIs, the most consistent patterns, the most boring version histories.

Go is like this. Rust is getting there. Elixir has been there for a decade.

The JavaScript ecosystem optimizes for something else --- for innovation, for new paradigms, for keeping up with what's possible. It's a different bet, and a legitimate one. But it's a bet that becomes more expensive as AI becomes a larger part of the development process.

Every Phoenix release that maintains backward compatibility makes the accumulated training data a little more valuable.

I don't know if this changes how ecosystems evolve. Stability has always been undervalued in software; there's no conference talk excitement in "we didn't break anything this year." The incentives push toward novelty.

But the calculus is shifting. AI assistance is too useful to ignore, and it works better with stable targets.

Elixir made a bet on boring before AI made it obvious. I'm not sure anyone outside the community has noticed yet.

Your turn

Let's compare notes

Got a different take, a story that backs this up, or a question it left open? Reply on X, or get in touch. I read every reply.

Newsletter

The Pragmatic CTO

Hard-won lessons on scaling teams and technology, from a CTO who's made the mistakes so you don't have to.

Subscribe

Further reading