#5
Type System / Developer Tooling

The Compiler Gets Smart: Elixir 1.20's Type Inference

Elixir 1.20 infers types across whole functions and guards — with no annotations required. This is a bigger deal than it sounds.

The first release candidate for Elixir 1.17 shipped a set-theoretic type system in 2024. Nobody had to change anything. 1.18 added pattern and return type inference. Still no annotations. Now 1.20-rc.3 has arrived, and the compiler infers types across whole functions, including guards — and you still don’t have to change anything. That’s the story. The Elixir team has been quietly doing something genuinely hard: making the type system come to your code, rather than making you rewrite your code for the type system.

Most type systems make a deal with you. Haskell’s deal is: annotate everything, and we’ll guarantee a lot. TypeScript’s deal is: annotate what you want, and we’ll do our best with the rest. Both deals ask you to change how you write code. Elixir’s bet is different — that enough can be inferred from how code already behaves that you can find real bugs without asking developers to write types at all. 1.20-rc.3 is the release that makes that bet look correct.

Here’s the concrete version. Take a function like this:

def sum_to_string(a, b) do
  Integer.to_string(a + b)
end

Prior to 1.20, Elixir couldn’t say much about a and b — they’re used with +, which accepts integers or floats. But Integer.to_string/1 only accepts integers. So the compiler now reasons backward: a + b produces whatever goes into that function, meaning a and b must both be integers. Call this with a float and you get a type warning. Without a single annotation.

Guard inference is the new part in rc.3, and it’s the piece that changes the most code at once. Guards like when is_list(x) and is_integer(y) already communicate type information to every human reading them. Now they communicate it to the compiler too. Guards that constrain tuple size let the compiler catch out-of-bounds elem/2 calls. Guards using is_map_key teach the compiler about map shape. And because clause matching is tracked across the whole function, if the first clause handles nil, the second clause knows the value can’t be nil — which means dead code that pattern-matches on impossible values now gets flagged automatically.

That last point matters more than it might sound. Dead clauses accumulate over years of refactoring. A module that once handled four possible shapes of data now only sees two, but nobody cleaned up the guards. The compiler now finds these for free, without any changes to existing code. This is the steady-state benefit: not the bugs you introduce today, but the accumulation you inherited.

On top of the type inference work, 1.20 ships meaningful compile-time improvements. On OTP 28, compilation is roughly 10% faster across the board. On OTP 29, around 20% faster. There’s also an experimental module_definition: :interpreted mode — evaluating modules instead of compiling them — that can cut compilation times by up to 5x in some architectures, scaling with available cores. The interpreted mode is experimental and not for all codebases, but the default compile improvements apply to everyone.

1.20 targets a May 2026 final release. Three release candidates are already out. The type system has been production-safe since 1.17 — warnings, not errors, and only when violations are certain. What’s changing with each release is the surface area: more constructs covered, more edge cases handled, more bugs found for free. The team has said the goal is to eventually make the type system “sound” — meaning every warning corresponds to a real possible runtime error. They’re not there yet. But rc.3 makes that goal look reachable in a way it didn’t before.

The headline for Elixir 1.20 isn’t “now Elixir has types.” It already had types, in the sense that everything has types at runtime. The headline is: the compiler is finally smart enough to find the places where those types don’t match, without asking you to spell them out. That’s a qualitatively different kind of language to maintain.