On this page28 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a Pattern Type?
- 2. Where We Try Them
- 3. Constant Patterns
- 4. Variable Patterns
- 5. The Wildcard Pattern
- 6. Relational Patterns
- 7. Logical-Or Patterns: ||
- 8. Logical-And Patterns: &&
- 9. Parenthesized Patterns
- 10. Cast Patterns: as
- 11. Null-Check Patterns: ?
- 12. Null-Assert Patterns: !
- 13. List Patterns
- 14. Record Patterns
- 15. Nesting Patterns
- 16. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 79 — Pattern Types
| Category | 13 — Patterns & Modern Dart |
| Lecture | 079 of 140 |
| Estimated video length | 12 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
You have already used
_,>= 90and'Sat' || 'Sun'without knowing they had names. Each one is a pattern type, and Dart has a dozen of them that click together like building blocks.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 013–015, 024–025, 076–078:
- nullable types, and the
!operator (Lectures 013–015) switchstatements and switch expressions, including_,||,>=andwhen(Lectures 024–025)- records such as
('Ali', 85)and(name: 'Sara', marks: 91)(Lecture 076) - what a pattern is, matching with
if (value case pattern)(Lecture 077) - destructuring with
var (name, marks) = record;(Lecture 078)
Connection With Previous Lectures
In Lecture 025 we wrote this, and it worked:
void main() {
var marks = 82;
var grade = switch (marks) {
>= 90 => 'A',
>= 80 => 'B',
_ => 'C',
};
print(grade);
}BBut what is >= 90? What else can go on the left of =>? Can a case check that a value
is not null, or that a list has exactly three items, and pull them out? Lectures 077–078
showed that patterns match and destructure. This lecture tours every pattern type, one at a
time.
Learning Objectives
By the end of this lecture you will be able to:
- Name the main pattern types and say what each one checks
- Use constant, variable, and wildcard patterns
- Match ranges with relational patterns and combine patterns with
||and&& - Group patterns with parentheses
- Handle nullable values with the null-check
?and null-assert!patterns - Convert a value with a cast pattern, and know when it throws
- Match lists and records by shape, including the rest element
... - Nest patterns inside each other to check a whole structure at once
1. What Is a Pattern Type?
A pattern describes a shape a value might have. Each pattern type is one kind of description. Here is the whole set for this lecture:
| Pattern type | Example | Matches when… |
|---|---|---|
| Constant | 5, 'red', null | the value equals the constant |
| Variable | var m, int n | always (if the type fits), and binds a name |
| Wildcard | _, int _ | always (if the type fits), binds nothing |
| Relational | >= 90, != 0 | the comparison is true |
| Logical-or | p1 || p2 | either side matches |
| Logical-and | p1 && p2 | both sides match |
| Parenthesized | (p1 || p2) | the inner pattern matches |
| Cast | var n as int | always — but it throws if the cast fails |
| Null-check | var n? | the value is not null |
| Null-assert | var n! | always — but it throws on null |
| List | [a, b, ...] | the value is a list of that shape |
| Record | (a, b), (name: n) | the value is a record of that shape |
Two more, object and map patterns, get their own lecture next.
2. Where We Try Them
Any pattern works after case in a switch, and after case in an if:
void main() {
var marks = 95;
if (marks case >= 90) {
print('Excellent');
}
}ExcellentRead it as "if marks matches the pattern >= 90".
3. Constant Patterns
A constant pattern matches when the value is equal to a constant: a number, a string,
true, false, null, or a const variable.
void main() {
const passMark = 50;
var marks = 50;
switch (marks) {
case 100:
print('Perfect');
case passMark:
print('Just passed');
default:
print('Other');
}
}Just passedThese are the plain case values from Lecture 024. A non-const variable is not allowed
here (see Mistake 1).
4. Variable Patterns
A variable pattern declares a new variable and puts the matched value in it. With a type,
like int n, it also checks the type first.
void main() {
Object value = 42;
switch (value) {
case int n:
print('A whole number: $n');
case String s:
print('Some text: $s');
default:
print('Something else');
}
}A whole number: 42Inside the first case n is an int, so n + 1 would work. var n and final n also
bind, without checking a type.
5. The Wildcard Pattern
The wildcard _ matches anything and binds nothing. A typed wildcard, int _, checks
the type and still binds nothing:
void main() {
Object value = 3.5;
var kind = switch (value) {
int _ => 'int',
double _ => 'double',
_ => 'other',
};
print(kind);
}doubleUse int _ when you care about the type but not the value.
6. Relational Patterns
A relational pattern is a comparison operator (==, !=, <, <=, >, >=) followed
by a constant. You met it in Lecture 025:
void main() {
var temperature = -3;
if (temperature case < 0) {
print('Freezing');
}
}FreezingThe value being matched is the left side of the comparison: temperature < 0.
7. Logical-Or Patterns: ||
p1 || p2 matches if either pattern matches. Dart tries the left side first.
void main() {
var day = 'Sun';
var kind = switch (day) {
'Sat' || 'Sun' => 'Weekend',
_ => 'Weekday',
};
print(kind);
}WeekendIf the sides declare variables, both sides must declare the same ones (Mistake 4).
8. Logical-And Patterns: &&
p1 && p2 matches only if both patterns match. It is how you write a range:
void main() {
var marks = 67;
if (marks case >= 50 && < 75) {
print('Pass');
}
}PassIt can also mix kinds: int n && > 0 checks the type, binds n, and checks it is positive.
9. Parenthesized Patterns
&& binds more tightly than ||, just like with bool values in Lecture 021. Parentheses
change the grouping:
void main() {
var marks = 101;
var label = switch (marks) {
(< 0 || > 100) => 'Invalid',
>= 50 => 'Pass',
_ => 'Fail',
};
print(label);
}InvalidHere they only add clarity; in int _ && (< 0 || > 100) they change the meaning.
10. Cast Patterns: as
A cast pattern var n as int tells Dart "treat this value as an int". It does not
check first: if the value is not an int, the program crashes.
void main() {
Object amount = 250;
if (amount case var n as int) {
print(n * 2);
}
}500Use it only when you are certain. Otherwise use int n, which simply fails to match.
11. Null-Check Patterns: ?
A null-check pattern var b? matches only when the value is not null, and the new
variable has the non-nullable type:
void showBonus(int? bonus) {
if (bonus case var b?) {
print('Bonus: ${b + 5}');
} else {
print('No bonus');
}
}
void main() {
showBonus(15);
showBonus(null);
}Bonus: 20
No bonusb is an int, not an int?, so b + 5 compiles. The value comes in as a parameter so
that Dart cannot know in advance whether it is null.
12. Null-Assert Patterns: !
A null-assert pattern var b! also gives a non-nullable variable, but on null it
throws, exactly like the ! operator from Lecture 015:
void showStock(int? stock) {
switch (stock) {
case var s!:
print('In stock: $s');
}
}
void main() {
showStock(8);
}In stock: 8Rule of thumb: ? means "skip if null"; ! means "I promise it's not null".
13. List Patterns
A list pattern [p1, p2, …] matches a list with exactly that many elements, and matches
each element with its own pattern. A rest element ... matches any number of elements:
void main() {
var marks = [90, 75, 82, 60];
switch (marks) {
case []:
print('No marks');
case [var only]:
print('One mark: $only');
case [var first, ..., var last]:
print('First $first, last $last');
}
}First 90, last 60[var first, ..., var last] means "two or more elements; bind the first and last".
...var middle would keep the skipped part as a list.
14. Record Patterns
A record pattern matches a record with the same shape. Named fields are written
name: pattern; :name is short for name: name.
void main() {
var result = (name: 'Sara', marks: 91);
switch (result) {
case (name: 'Ali', :var marks):
print('Ali scored $marks');
case (:var name, marks: >= 90):
print('$name got an A');
default:
print('Keep going');
}
}Sara got an AThe second case checks the field marks with a relational pattern and binds name.
15. Nesting Patterns
Every pattern type can sit inside a list or record pattern. That is where they get powerful:
void main() {
var order = ('Zayan', [1200, 450]);
if (order case (String who, [int a, int b]) when a + b > 1000) {
print('$who spent ${a + b}');
}
}Zayan spent 1650One line checks the record shape, the types, the list length, and a when guard. Guards and switch exhaustiveness get a full lecture in Lecture 081.
16. A Real-World Example
Sara's school receives each result as a record of a name and a nullable list of marks:
void main() {
var results = [
('Ali', [80, 75, 85]),
('Sara', <int>[]),
('Zayan', null),
('Hina', [101]),
];
for (var r in results) {
var report = switch (r) {
(var name, null) => '$name: absent',
(var name, []) => '$name: no marks yet',
(var name, [(< 0 || > 100)]) => '$name: invalid mark',
(var name, [var m]) => '$name: one mark, $m',
(var name, [var first, ...var rest]) =>
'$name: $first and ${rest.length} more',
};
print(report);
}
}Ali: 80 and 2 more
Sara: no marks yet
Zayan: absent
Hina: invalid markEight pattern types work together, and Dart confirms the arms cover every case.
Code Examples
Example 1 — Constant Patterns
void main() {
var light = 'red';
switch (light) {
case 'green':
print('Go');
case 'red':
print('Stop');
default:
print('Wait');
}
}StopExample 2 — Typed Variable Patterns
void main() {
Object item = 'Notebook';
var text = switch (item) {
int n => 'Number $n',
String s => 'Text of length ${s.length}',
_ => 'Unknown',
};
print(text);
}Text of length 8Example 3 — Relational and Logical-And
void main() {
var age = 15;
var ticket = switch (age) {
< 5 => 'Free',
>= 5 && < 18 => 'Child',
_ => 'Adult',
};
print(ticket);
}ChildExample 4 — Logical-Or
void main() {
var month = 12;
if (month case 12 || 1 || 2) {
print('Winter');
}
}WinterExample 5 — Null-Check vs Null-Assert
void greet(String? nickname) {
if (nickname case var n?) {
print('Hi, $n');
} else {
print('No nickname');
}
}
void showCity(String? city) {
switch (city) {
case var c!:
print('City: $c');
}
}
void main() {
greet(null);
showCity('Lahore');
}No nickname
City: LahoreExample 6 — Cast Pattern
void main() {
Object price = 99.5;
if (price case var p as double) {
print(p.round());
}
}100Example 7 — Student Marks with a List Pattern
void main() {
var marks = [88, 92, 79];
if (marks case [var a, var b, var c]) {
print('Ali total: ${a + b + c}');
}
}Ali total: 259The pattern needs exactly three elements; with four marks, nothing would print.
Example 8 — Shopping Total with Rest
void main() {
var prices = [1200, 450, 300, 50];
if (prices case [var first, ...var others]) {
var total = first;
for (var p in others) {
total += p;
}
print('Items: ${others.length + 1}, total: $total');
}
}Items: 4, total: 2000first is 1200, and ...var others collects the rest as the list [450, 300, 50].
Example 9 — Bank Balance with a Record Pattern
void main() {
var account = (owner: 'Sara', balance: -200);
var message = switch (account) {
(:var owner, balance: < 0) => '$owner is overdrawn',
(:var owner, balance: 0) => '$owner has nothing left',
(:var owner, :var balance) => '$owner has $balance',
};
print(message);
}Sara is overdrawnHow it works
:var ownerbinds the fieldownerto a variable with the same name.balance: < 0checks the field with a relational pattern;-200matches.- The last arm binds both fields, so it catches every other record.
Example 10 — Temperature Readings
void main() {
var readings = [('Mon', 31.5), ('Tue', null), ('Wed', -2.0)];
for (var r in readings) {
var text = switch (r) {
(var day, null) => '$day: sensor offline',
(var day, var t? && < 0) => '$day: $t, freezing',
(var day, var t?) => '$day: $t',
};
print(text);
}
}Mon: 31.5
Tue: sensor offline
Wed: -2.0, freezingHow it works
nullis a constant pattern for the offline case.var t? && < 0checks "not null" and "below zero" on the same field.- The last arm catches every remaining non-null reading.
Common Beginner Mistakes
1. Using a normal variable as a constant pattern
void main() {
var passMark = 50;
var marks = 50;
switch (marks) {
case passMark:
print('Just passed');
default:
print('Other');
}
}Error: Not a constant expression.
case passMark:
^^^^^^^^A name after case must be a constant. Declare it const passMark = 50;.
2. A null-assert pattern on a null value
void main() {
int? bonus;
switch (bonus) {
case var b!:
print('Bonus: $b');
}
}Unhandled exception:
Null check operator used on a null valueIt compiles but crashes. Use var b? when null is possible.
3. A cast pattern on the wrong type
void main() {
Object value = 'Ali';
switch (value) {
case var n as int:
print(n + 1);
}
}Unhandled exception:
type 'String' is not a subtype of type 'int' in type castas never fails to match; it throws. Use int n to test the type instead.
4. Different variables on the two sides of ||
void main() {
var point = (0, 5);
switch (point) {
case (var x, 0) || (0, var y):
print('On an axis');
default:
print('Elsewhere');
}
}Error: Variable pattern 'y' is missing in this branch of the logical-or pattern.
Try declaring this variable pattern in the branch.
case (var x, 0) || (0, var y):
^Both sides must bind the same names, such as (var n, 0) || (0, var n).
5. Forgetting that a list pattern checks the length
[var a, var b] does not match [80, 75, 90]. Add ... when the length can vary.
Best Practices
- Prefer
int n(test) overvar n as int(crash) unless the type is guaranteed. - Prefer
var x?overvar x!; save!for values that truly cannot benull. - Put the most specific arms first:
[]and[var one]before[var first, ...]. - Use
_orint _for parts you do not need, instead of inventing unused names. - Add parentheses whenever you mix
||and&&, even when they are not required.
Mini Challenge
Write a program that describes a cart, stored as a List<int> of prices. Print
Empty cart for [], One item: P for a single price, and N items, first P otherwise.
Test it with [], [500] and [300, 200, 100].
Empty cart
One item: 500
3 items, first 300Try it yourself before opening the solution.
Show solution
void main() {
var carts = [
<int>[],
[500],
[300, 200, 100],
];
for (var cart in carts) {
var text = switch (cart) {
[] => 'Empty cart',
[var p] => 'One item: $p',
[var first, ...] => '${cart.length} items, first $first',
};
print(text);
}
}Empty cart
One item: 500
3 items, first 300[] matches zero elements, [var p] exactly one, and [var first, ...] one or more, so
the three arms cover every list.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Store
var answer = 'yes';and use a switch expression with constant patterns to printAgreedfor'yes',Refusedfor'no', andUnclearotherwise. - Store
Object value = true;and use typed variable patterns to printbool: true,int: …orother. - Store
var speed = 72;and useif-casewith a relational pattern to printToo fastwhenspeed > 60.
Application
- Store
var marks = 45;and use a switch expression with&&patterns:0–49givesFail,50–100givesPass, anything elseInvalid. - Write
void pay(int? discount)that uses a null-check pattern to printPrice: 90for a price of100and a discount of10, orNo discountfornull. Call it with both. - Store
var scores = [7, 9, 4, 8];and use a list pattern with a rest element to printFirst: 7, Last: 8. - Store
var student = ('Ali', 88);and use a record pattern to printAli passedwhen the mark is>= 50, orAli failedotherwise.
Challenge
- Store
var day = 'Tue';andvar hour = 20;. Use a switch on the record(day, hour)with logical-or and relational patterns to printWeekendfor'Sat'or'Sun',Eveningfor a weekday hour>= 18, andWorking hoursotherwise. - Store
var accounts = [(owner: 'Sara', balance: 500), (owner: 'Zayan', balance: null)];Loop over it and printSara: 500orZayan: closed, usingnulland?patterns. - Store
var data = [3, 'Ali', 2.5, [1, 2]];(aList<Object>). Loop over it and printsmall intfor anintbelow10,name Alifor aString,pair 1 2for a list of exactly two elements, andotherfor anything else.
Practice Answers
Question 1 — Solution
void main() {
var answer = 'yes';
var text = switch (answer) {
'yes' => 'Agreed',
'no' => 'Refused',
_ => 'Unclear',
};
print(text);
}AgreedQuestion 2 — Solution
void main() {
Object value = true;
var text = switch (value) {
bool b => 'bool: $b',
int n => 'int: $n',
_ => 'other',
};
print(text);
}bool: trueQuestion 3 — Solution
void main() {
var speed = 72;
if (speed case > 60) {
print('Too fast');
}
}Too fastQuestion 4 — Solution
void main() {
var marks = 45;
var result = switch (marks) {
>= 0 && < 50 => 'Fail',
>= 50 && <= 100 => 'Pass',
_ => 'Invalid',
};
print(result);
}FailQuestion 5 — Solution
void pay(int? discount) {
var price = 100;
if (discount case var d?) {
print('Price: ${price - d}');
} else {
print('No discount');
}
}
void main() {
pay(10);
pay(null);
}Price: 90
No discountQuestion 6 — Solution
void main() {
var scores = [7, 9, 4, 8];
if (scores case [var first, ..., var last]) {
print('First: $first, Last: $last');
}
}First: 7, Last: 8Question 7 — Solution
void main() {
var student = ('Ali', 88);
var text = switch (student) {
(var name, >= 50) => '$name passed',
(var name, _) => '$name failed',
};
print(text);
}Ali passedQuestion 8 — Solution
void main() {
var day = 'Tue';
var hour = 20;
var text = switch ((day, hour)) {
('Sat' || 'Sun', _) => 'Weekend',
(_, >= 18) => 'Evening',
_ => 'Working hours',
};
print(text);
}EveningQuestion 9 — Solution
void main() {
var accounts = [
(owner: 'Sara', balance: 500),
(owner: 'Zayan', balance: null),
];
for (var a in accounts) {
var text = switch (a) {
(:var owner, balance: null) => '$owner: closed',
(:var owner, balance: var b?) => '$owner: $b',
};
print(text);
}
}Sara: 500
Zayan: closedQuestion 10 — Solution
void main() {
var data = <Object>[
3,
'Ali',
2.5,
[1, 2],
];
for (var item in data) {
var text = switch (item) {
int _ && < 10 => 'small int',
String s => 'name $s',
[var a, var b] => 'pair $a $b',
_ => 'other',
};
print(text);
}
}small int
name Ali
other
pair 1 22.5 is a double, so only the wildcard matches it.
Quick Revision
- What is the difference between a constant pattern and a variable pattern?
- What does
int _check, and what does it bind? - Write a pattern that matches numbers from 10 up to, but not including, 20.
- Which binds more tightly in a pattern:
||or&&? - What happens when a cast pattern's type is wrong?
- How is
var x?different fromvar x!? - What does
...mean inside a list pattern? - What is
:var marksshort for in a record pattern?
Key Takeaways
- A pattern type is one kind of shape description; patterns nest inside each other.
- Constant patterns compare with
==; variable patterns bind a name and can test a type (int n); the wildcard_matches anything and binds nothing. - Relational patterns compare with
<,>=,!=…;||and&&combine patterns, and parentheses group them. - Null-check
?skipsnull; null-assert!and castasthrow when wrong. - List patterns check the length unless they contain a rest element
.... - Record patterns match positional and named fields;
:namebinds a field by its name.
What's Next?
Lecture 80 — Object & Map Patterns. Every pattern today looked at numbers, strings, lists
and records. Real programs are full of objects — a Student with a name and marks, a
Map read from a file. Next we will match objects by their getters with Student(:var name)
and maps by their keys with {'name': var n}, using all the pattern types from this lecture
inside them.