Pattern matching arrives everywhere at once
Java, Python, C#, and JavaScript are all converging on the same feature from ML languages, thirty years late.
Four mainstream languages have shipped or are shipping structural pattern matching in the last several years. The feature is forty years old and came from ML and Haskell, and it is worth understanding why it took this long and what it actually buys.
what it is#
Destructuring and dispatch in one construct: match a value against a shape, bind its parts to names, and branch on which shape matched.
Java:
String describe(Shape s) {
return switch (s) {
case Circle c when c.radius() > 100 -> "big circle";
case Circle c -> "circle r=" + c.radius();
case Rect(int w, int h) when w == h -> "square " + w;
case Rect(int w, int h) -> "rect " + w + "x" + h;
};
}Python:
match command.split():
case ["move", direction]:
move(direction)
case ["take", *items] if items:
for item in items: take(item)
case _:
unknown()Both are doing the same thing: testing structure, extracting components, and binding them, in one expression.
why it matters more than it looks#
Exhaustiveness checking. This is the actual payoff and it is easy to miss.
If your type system knows the complete set of possible shapes — a sealed hierarchy in Java, a union type in TypeScript, an enum in Rust — the compiler can verify that your match handles all of them.
Which means: add a new case to your data model, and the compiler tells you every place that needs updating.
That is a qualitatively different maintenance experience. Without it, adding a new variant means grepping for every switch statement and hoping. With it, the build fails until you have handled it everywhere.
This is the single largest practical benefit of algebraic data types and most discussion of pattern matching skips it entirely in favor of syntax.
It replaces the visitor pattern. An entire design pattern existed because object-oriented languages could not dispatch on structure. That pattern is a significant amount of ceremony — an interface, an accept method on every node, a visitor implementation — to express what pattern matching says in five lines.
If you have a visitor in your codebase and your language now has pattern matching, you can delete a lot of code.
It makes illegal states harder to express. Combined with sum types, you can model "this is one of exactly these things" and have the compiler enforce it, rather than a class with six nullable fields where only certain combinations are valid.
the caveats#
Python's match is not exhaustiveness-checked. It is a runtime construct. Without a sealed type system there is nothing to check against. Type checkers can do some of this with Literal and Union types and it is not enforced by the language.
That makes Python's version considerably less valuable than Java's or Rust's — it is nicer destructuring syntax rather than a correctness tool.
Capture semantics surprise people. In Python, a bare name in a pattern binds, it does not compare. This is the number one confusion:
match color:
case RED: # binds `color` to RED — matches everything!
...
case Color.RED: # this compares
...A dotted name compares. A bare name binds. Everybody gets this wrong once.
Overuse. Pattern matching is satisfying and it is possible to write a match statement where an if would be clearer. If you are matching on one condition, use an if.
the trend it is part of#
Pattern matching is one of several ideas migrating from functional languages into the mainstream, along with immutability by default, expression-oriented syntax, Option/Result types instead of null and exceptions, and real sum types.
Each of these took decades to cross over. The reason is not that the ideas were unknown — it is that mainstream languages have compatibility obligations that make adding a feature to an existing type system extraordinarily hard, and it takes a generation of language designers who grew up with the ideas to do the work.
The next ones in the queue, visibly: effect systems, better concurrency abstractions in the type system, and some form of ownership tracking outside of Rust.
Give it ten years.
— Dom, March 23, 2026