Simplifying Conditional Logic in Dart 3 with Pattern Matching
Dart 3 pattern matching lets you deconstruct objects and handle heterogeneous data in a single switch expression, cutting boilerplate and adding compile-time exhaustiveness checks.
28 May 2026, 01:25 UTC

The problem: tangled if-else chains
When a codebase grows, conditional logic that inspects the runtime type of an object often turns into a cascade of if (obj is Type) blocks. Each branch repeats the type test, the cast, and the field access, making the code noisy and easy to get wrong when a new subclass appears.
Pattern matching in a nutshell
Dart 3 introduces pattern matching, a single syntactic construct that can deconstruct an object, check its type, and bind its fields to local variables all at once. The feature works with class patterns, list patterns, map patterns, and guard clauses, and it integrates directly with switch expressions so the compiler can verify exhaustiveness.
Worked example: describing a config hierarchy
Assume a small sealed hierarchy that models a configuration payload:
sealed class Config {}
class DatabaseConfig extends Config {
final String host;
final int port;
DatabaseConfig(this.host, this.port);
}
class CacheConfig extends Config {
final String redisUrl;
final int ttl;
CacheConfig(this.redisUrl, this.ttl);
}
class FeatureFlagConfig extends Config {
final Map<String, bool> flags;
FeatureFlagConfig(this.flags);
}A single switch expression replaces the whole if-else chain:
String describe(Config cfg) => switch (cfg) {
DatabaseConfig(host: var h, port: var p) => 'DB at $h:$p',
CacheConfig(redisUrl: var u, ttl: var t) => 'Cache $u ttl=$t',
FeatureFlagConfig(flags: var f) => 'Flags: ${f.keys}',
};Each case pattern names the fields it cares about and binds them to fresh variables. No explicit casts, no repeated is checks.
What the compiler gives you
- Exhaustiveness checking: if you add a new
Configsubclass and forget a case, the analyzer reports a non-exhaustive switch. - Comparable runtime cost: the compiler emits the same kind of type tests and field reads you would write by hand, so the syntax carries no inherent runtime penalty.
- Guard clauses: you can refine a case with
when, for exampleDatabaseConfig(host: var h, port: var p) when p > 1024 => ....
Trade-offs and limits
Pattern matching requires Dart SDK 3.0.0 or later, so projects locked to older versions cannot adopt it without an SDK upgrade. Deeply nested patterns can also become hard to read; a team style guide that caps nesting and keeps variable names meaningful helps the code stay clear.
How to verify it works
- In a Dart project whose
pubspec.yamlcontainsenvironment: sdk: '>=3.0.0 <4.0.0', save the example above (plus a smallmainthat printsdescriberesults) asbin/pattern_demo.dart. - From the project root, run
dart run bin/pattern_demo.dart. Expected check: the description strings print with no compile errors. - Run
dart analyze. Then comment out one switch case and run it again: the analyzer should report a non-exhaustive switch, confirming compile-time checking. - Optionally, compile with
dart compile kernel bin/pattern_demo.dart -o /tmp/pattern.dillto inspect the generated kernel. Risk note: this writes to the output path, so ensure the directory is writable. None of these commands modify source files, so no rollback is needed.
Next steps
Start with the most tangled if-else blocks in your codebase, especially those handling sealed hierarchies or JSON-like maps, and convert them to switch expressions. The result is shorter, safer conditional logic that the compiler helps you keep complete as the code evolves.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.