I've only been building real things for about a year. TypeScript was my entry point — types were already there when I started shipping code, so I never had to choose them. They were just the default shape of how code looked.
That made it interesting when Elixir — a language built around not having types — shipped v1.20 this week as "a gradually typed language."
What Just Shipped
Elixir is a functional, dynamic language that runs on the Erlang VM. It's known for concurrency, fault tolerance, and a pipe-based style that reads almost like English. The whole aesthetic of the language is flow — data moving through transformations, clean and composable. Types weren't the point.
v1.20 ships a type system that runs inference and checking across every Elixir program — no annotations required. The types are inferred from what your code actually does. Pattern match on a value and the type narrows for each branch. Access a map key and the system updates its model of what that variable looks like. Reach an impossible combination and it flags dead code before it ships.
The centerpiece type is called dynamic(). This is explicitly not TypeScript's any.
The difference matters. any in TypeScript is an escape hatch — it tells the type checker "I know what I'm doing, step back." Once you mark something any, the checker stops tracking. It trusts you. That's powerful for exactly that reason: it gets the checker off your back. But any is also contagious. Let it into a function signature and it leaks into the return type. Let it into a utility function and it spreads through every caller. TypeScript even ships a compiler flag — noImplicitAny — specifically because teams kept letting it creep in without noticing.
dynamic() works differently. It doesn't close the file — it writes "TBD, will update." A variable typed dynamic() is one the checker doesn't fully know yet. As it watches the code, it keeps refining: "okay, this is used as a map with keys a and b," "this gets pattern-matched in a case, so track each branch separately." It stays in the room. It keeps updating.
On the If T: Benchmark for Type Narrowing — a standardized test for how precisely a language recovers type information in dynamic code — Elixir v1.20 passes 12 of 13 categories. That's a serious result for something that just shipped. Function signatures and generics are still in progress, but the narrowing core is live, running on every Elixir program today.
The Longer Pattern
Elixir isn't doing anything unusual here. It's the latest in a sequence that's been running for a decade.
Python's type hints landed in 3.5 (2015). Mypy showed up as the first major checker. Then Pyright. By somewhere around 2023, the Python community's implicit norm had quietly shifted from "types if you want" to "you probably should unless you have a reason not to" — no announcement, just developers reaching for them until they became expected.
Ruby got Sorbet from Stripe in 2019. The core team added the official RBS type notation in Ruby 3.0 (2020). PHP added type declarations starting in PHP 7 (2015) and has been expanding them every major release since.
JavaScript didn't get types. TypeScript got popular enough that a huge portion of the JavaScript ecosystem now runs it instead.
Every major dynamic language has either adopted a gradual type system, built one from scratch, or produced a typed variant that developers migrated toward. None of it was mandated. It just happened, one codebase at a time.
What I Actually Notice
Here's my take, offered with full awareness that I haven't written much Elixir:
The interesting thing about dynamic() vs any isn't the syntax. It's what the design choice says about who learned what. TypeScript shipped any when gradual typing was newer territory — give developers an escape valve, trust them not to abuse it. Teams abused it. The noImplicitAny compiler flag exists because the experience of real codebases, at scale, converged on: escape hatches get overused.
Elixir is building a type system in 2026, after watching that play out. dynamic() — a type that narrows rather than escapes — looks like a considered response to that lesson. Not "trust you to know what you're doing," but "let me keep watching and tell you what I figure out."
I might be reading too much into one design decision. Type systems always surface failure modes you won't predict until you're actually writing the code. But the principle — updating a prior vs abandoning tracking altogether — seems like the right direction. One closes the case early. The other stays curious.
From where I'm standing, what I actually notice is this: I came into programming when "should we use types?" already felt like a settled question. TypeScript was everywhere. The answer felt obvious. And from here, watching languages that had the debate and resolved it — each one arriving at "gradual types, yes" via a different path — makes the result look less like a trend and more like a convergence.
The debate isn't over in comment sections. It's over in roadmaps.
One Last Thing
There's a version of this story where Elixir's type system never becomes fully idiomatic — where the community uses it for library authors and not much else, and day-to-day Elixir stays annotation-free. That's genuinely possible. Python's type hints are still optional, and plenty of Python code ships without them.
But the capability existing changes things. Once a language has a type checker, it becomes the lens for writing public APIs, for structuring library interfaces, for onboarding someone new to a codebase. The tool shapes what gets built around it.
Elixir didn't have to add types. The community was building real systems without them.
They added them anyway.
That's probably the data point.