On this page22 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is a Pattern?
- 2. You Have Already Used Patterns
- 3. Job One: Matching
- 4. Job Two: Destructuring
- 5. Both Jobs at Once: case int n
- 6. The if-case Statement
- 7. Adding a Guard with when
- 8. Where Patterns Appear
- 9. Patterns That May Fail, Patterns That Must Fit
- 10. 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 77 — Introduction to Patterns
| Category | 13 — Patterns & Modern Dart |
| Lecture | 077 of 140 |
| Estimated video length | 9 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
A condition can only answer yes or no, and pulling values out of a record takes one line per field. A pattern does both jobs at once: it checks whether a value has the right shape, and it takes the value apart into named variables.
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 010, 024, 025, 027 and 076:
- type tests with
is, and how Dart promotes a variable after the test switchstatements and switch expressions, including||,>= 90andwhenfor-inloops- records such as
('Ali', 82), and reading their fields with$1and$2
Connection With Previous Lectures
Lecture 076 gave us records, so one value can carry a name and a mark together. Using one still takes a line per field, plus a separate check:
void main() {
var result = ('Ali', 82);
var name = result.$1;
var marks = result.$2;
if (marks >= 50) {
print('$name passed');
}
}Ali passed$1 and $2 say nothing about what they hold, and the "is it valid?" test is written apart
from the "get the values out" code. Back in Lecture 025 you met a tool that already mixes
checking with choosing: >= 90 => inside a switch. That tool has a name, and it reaches much
further than switches.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what a pattern is
- Recognize the patterns you already used in Lectures 024 and 025
- Describe the two jobs of a pattern: matching and destructuring
- Match and bind in one step with
case int n - Write an if-case statement such as
if (value case int n when n > 0) - List the five places where patterns can appear
- Tell a pattern that may fail from one that must always match
- Recognize the errors Dart gives for common pattern mistakes
1. What Is a Pattern?
A pattern is a description of a shape that a value is compared with. If the value fits the shape, the pattern matches, and it can also pull pieces of the value out into variables.
Analogy — think of a form with blanks:
Name: ____ Marks: ____. A sheet of paper fits the form only if it has those two fields in that order. When it fits, you copy what is written into the blanks.
void main() {
var (name, marks) = ('Ali', 82);
print('$name has $marks marks');
}Ali has 82 marks(name, marks) on the left is a pattern. It has the same shape as the record on the right,
so Dart fills the blanks: name gets 'Ali' and marks gets 82.
2. You Have Already Used Patterns
Every value after case, and every arm before =>, is a pattern:
void main() {
var marks = 82;
var day = 'Sunday';
var grade = switch (marks) {
>= 90 => 'A',
>= 80 => 'B',
_ => 'C',
};
var kind = switch (day) {
'Saturday' || 'Sunday' => 'Weekend',
_ => 'Weekday',
};
print('$grade, $kind');
}B, Weekend>= 90 is a relational pattern, 'Saturday' is a constant pattern, || joins two
patterns, and _ is the wildcard that matches anything. Lecture 025 introduced them inside
switches; this category shows that they work in many more places.
3. Job One: Matching
Matching asks one question: does this value fit the shape? The answer is yes or no.
void main() {
var temperature = 38.5;
var label = switch (temperature) {
< 0 => 'Freezing',
>= 0 && < 25 => 'Mild',
_ => 'Hot',
};
print(label);
}Hot38.5 does not fit < 0, does not fit >= 0 && < 25, and fits _. Dart tries the
patterns from top to bottom and stops at the first match.
4. Job Two: Destructuring
Destructuring means taking a value apart into separate variables, as the form example in section 1 did:
void main() {
var account = ('Sara', 1500.0);
var (owner, balance) = account;
print('Owner: $owner');
print('Balance: $balance');
}Owner: Sara
Balance: 1500.0One line replaces account.$1 and account.$2, and the variables get meaningful names.
We will study destructuring properly — records, lists, and nested values — in Lecture 078.
5. Both Jobs at Once: case int n
A pattern can check a value and give it a name in one step. int n means "if the value
is an int, call it n":
void main() {
Object value = 42;
switch (value) {
case int n:
print('A whole number: ${n + 1}');
case String s:
print('Some text: ${s.toUpperCase()}');
default:
print('Something else');
}
}A whole number: 43Inside the first case, n is an int, so n + 1 works without a cast. Compare Lecture 010,
where you needed if (value is int) and then relied on promotion. The pattern does the test
and creates a well-typed variable together.
6. The if-case Statement
A switch is a lot of code for one check. The if-case statement tests a value against a single pattern:
void main() {
Object input = 'Zayan';
if (input case String name) {
print('Hello, $name');
}
}Hello, ZayanRead it as "if input matches the pattern String name". The shape is always
if (value case pattern). When the pattern matches, the body runs and name is available
inside it.
7. Adding a Guard with when
The when guard from Lecture 025 works in an if-case too. The body runs only if the pattern
matches and the condition is true:
void main() {
Object value = 7;
if (value case int n when n > 0) {
print('$n is a positive whole number');
} else {
print('Not a positive whole number');
}
}7 is a positive whole numberChange value to -3 or '7' and the else branch runs: -3 fails the guard, and '7'
fails the pattern because it is a String.
8. Where Patterns Appear
Patterns can be written in five places:
| Place | Example |
|---|---|
switch statement case | case int n: |
| switch expression arm | >= 90 => 'A', |
| if-case | if (value case int n when n > 0) |
| variable declaration | var (name, marks) = result; |
for-in loop | for (var (name, marks) in results) |
The last one destructures every item of a list as the loop reaches it:
void main() {
var results = [('Ali', 72), ('Sara', 95), ('Zayan', 88)];
for (var (name, marks) in results) {
print('$name scored $marks');
}
}Ali scored 72
Sara scored 95
Zayan scored 88Lecture 050 promised that a map entry could be unpacked straight in the loop header. That is a pattern too:
void main() {
var marks = {'Ali': 72, 'Sara': 95};
for (var MapEntry(:key, :value) in marks.entries) {
print('$key: $value');
}
}Ali: 72
Sara: 95MapEntry(:key, :value) is an object pattern; we take it apart in Lecture 080 — Object &
Map Patterns.
9. Patterns That May Fail, Patterns That Must Fit
In a switch or an if-case, a pattern is allowed to fail — Dart simply tries the next case
or runs the else. In a variable declaration or a for-in loop there is no "next case", so
the pattern must always match, and Dart checks this before the program runs:
void main() {
var (name, marks) = ('Ali', 82, 'B'); // ✗ three fields, two blanks
print(name);
}Error: The matched value of type '(String, int, String)' isn't assignable to the required type '(Object?, Object?)'.
- 'Object' is from 'dart:core'.
Try changing the required type of the pattern, or the matched value type.
var (name, marks) = ('Ali', 82, 'B');
^The record has three fields; the pattern has room for two. Lecture 079 — Pattern Types names every kind of pattern and where each may be used.
10. A Real-World Example
A bank keeps today's transactions as records. Patterns unpack each one, sort it by size, and check a value of unknown type:
void main() {
var transactions = [('Ali', 250.0), ('Sara', -1200.0), ('Zayan', 5000.0)];
for (var (name, amount) in transactions) {
var size = switch (amount) {
< 0 => 'withdrawal',
>= 1000 => 'large deposit',
_ => 'deposit',
};
print('$name: $size of $amount');
}
Object limit = 3000.0;
if (limit case double max when max > 0) {
print('Daily limit: $max');
}
}Ali: deposit of 250.0
Sara: withdrawal of -1200.0
Zayan: large deposit of 5000.0
Daily limit: 3000.0Three of the five places in one program: a for-in pattern destructures, a switch expression
matches, and an if-case matches and binds with a guard.
Code Examples
Example 1 — Destructuring a Record
void main() {
var (city, temperature) = ('Lahore', 31);
print('$city: $temperature°C');
}Lahore: 31°CExample 2 — A Constant Pattern
void main() {
var answer = 'yes';
var reply = switch (answer) {
'yes' => 'Confirmed',
'no' => 'Cancelled',
_ => 'Unknown',
};
print(reply);
}ConfirmedExample 3 — A Relational Pattern
void main() {
var speed = 70;
var verdict = switch (speed) {
> 60 => 'Too fast',
_ => 'OK',
};
print(verdict);
}Too fastExample 4 — Matching by Type
void main() {
Object item = 2.5;
switch (item) {
case int n:
print('int $n');
case double d:
print('double $d');
default:
print('other');
}
}double 2.5Example 5 — if-case Without a Guard
void main() {
Object nickname = 'Zee';
if (nickname case String s) {
print('${s.length} letters');
}
}3 lettersExample 6 — if-case With a Guard and else
void main() {
Object age = 15;
if (age case int years when years >= 18) {
print('Adult');
} else {
print('Not an adult');
}
}Not an adult15 is an int, so the pattern matches, but the guard is false.
Example 7 — Student Marks
void main() {
var students = [('Ali', 45), ('Sara', 91), ('Zayan', 78)];
for (var (name, marks) in students) {
var grade = switch (marks) {
>= 90 => 'A',
>= 70 => 'B',
>= 50 => 'C',
_ => 'F',
};
print('$name: $grade');
}
}Ali: F
Sara: A
Zayan: BHow it works
- The loop pattern
(name, marks)unpacks each record. - The switch tries the relational patterns from strictest to widest.
45fails all three, so_givesF.
Example 8 — Shopping Total
void main() {
var cart = [('Notebook', 500, 2), ('Pen', 50, 4)];
var total = 0;
for (var (item, price, quantity) in cart) {
print('$item x$quantity = ${price * quantity}');
total += price * quantity;
}
print('Total: $total');
}Notebook x2 = 1000
Pen x4 = 200
Total: 1200How it works
- Each record has three fields, so the pattern has three variables.
- The names
item,priceandquantityreplace$1,$2and$3. totaladds up each line as the loop runs.
Example 9 — Bank Balance Check
void main() {
Object balance = 1500;
if (balance case int amount when amount >= 1000) {
print('Free transfers: balance $amount');
} else {
print('Transfer fee applies');
}
}Free transfers: balance 1500How it works
balanceis declared asObject, so Dart does not know it is a number.int amountmatches because the value is anint, and names itamount.- The guard
amount >= 1000istrue, so the first branch runs.
Example 10 — Temperature Readings
void main() {
var readings = [('Mon', -2.0), ('Tue', 18.5), ('Wed', 33.0)];
for (var (day, celsius) in readings) {
var feel = switch (celsius) {
< 0 => 'freezing',
>= 0 && < 25 => 'mild',
_ => 'hot',
};
print('$day is $feel');
}
}Mon is freezing
Tue is mild
Wed is hotHow it works
- The
for-inpattern destructures each(day, celsius)record. >= 0 && < 25joins two relational patterns into one range.- Only values
25and above reach_.
Common Beginner Mistakes
1. Using an if-case variable outside the if
void main() {
Object value = 'Ali';
if (value case String s) {
print(s);
}
// print(s); // ✗
}Error: Undefined name 's'.
print(s);
^A variable created by a pattern exists only inside the body that runs when it matches.
2. Writing a plain variable name as a case
void main() {
var limit = 50;
var marks = 82;
switch (marks) {
// case limit: // ✗
case >= 50:
print('Passed the limit of $limit');
}
}Error: Not a constant expression.
case limit:
^^^^^A bare name in a case is read as a constant pattern, and limit is not const. Use a
relational pattern (>= 50), a const value, or a guard (case int m when m >= limit).
3. A declaration pattern that does not fit
void main() {
// var (name, marks) = ('Ali', 82, 'B'); // ✗
}Error: The matched value of type '(String, int, String)' isn't assignable to the required type '(Object?, Object?)'.A declaration has no "next case", so the shape must match exactly.
4. Putting a wide pattern first
>= 50 before >= 90 catches every passing mark, so no one ever gets an A. Order patterns
from strictest to widest.
Best Practices
- Destructure records into named variables instead of reading
$1and$2repeatedly. - Use an if-case for one pattern; use a switch when there are several.
- Keep guards short — the pattern should do the shape work, the guard the extra condition.
- Give pattern variables meaningful names (
marks, notx). - End a switch expression with
_unless every value is already covered.
Mini Challenge
Store Object reading = 41.5;. With one if-case, print High fever: 41.5 when the value is a
double above 39, and Normal otherwise. Then destructure ('Sara', 37.0) into patient
and temp and print Sara: 37.0.
Your program should print exactly:
High fever: 41.5
Sara: 37.0Try it yourself before opening the solution.
Show solution
void main() {
Object reading = 41.5;
if (reading case double t when t > 39) {
print('High fever: $t');
} else {
print('Normal');
}
var (patient, temp) = ('Sara', 37.0);
print('$patient: $temp');
}High fever: 41.5
Sara: 37.0How it works
double tmatches because41.5is adouble, and the guardt > 39istrue.- The declaration pattern
(patient, temp)has the same two-field shape as the record.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Destructure
('Ali', 20)intonameandage, and printAli is 20. - Use a switch expression with relational patterns to turn
speed = 45intoSlow(below 50) orFast. Print the result. - Store
Object value = 'Dart';. Use an if-case to print the text in capitals.
Application
- Store
Object value = 12;. Use an if-case with a guard to print12 is evenonly when the value is an evenint. - Loop over
[('Ali', 3), ('Sara', 5)]with afor-inpattern and printAli has 3 booksandSara has 5 books. - Write a switch statement on
Object item = true;with casesint n,bool banddefault. Printbool true. - Use one
||pattern to printVowelforletter = 'e'when it is'a','e','i','o'or'u', andConsonantotherwise. - Destructure
('Notebook', 500, 3)into three variables and printTotal: 1500.
Challenge
- Loop over
[('Ali', 1200.0), ('Sara', -300.0), ('Zayan', 0.0)]. For each record printcredit,debit, oremptyusing a switch expression with> 0,< 0and_, in the formAli: credit. - Store
Object input = -8;. Use one if-case for a positiveint(printPositive: n) and anelse ifwith another if-case for a negativeint(printNegative: n); otherwise printNot a whole number.
Practice Answers
Question 1 — Solution
void main() {
var (name, age) = ('Ali', 20);
print('$name is $age');
}Ali is 20Question 2 — Solution
void main() {
var speed = 45;
var label = switch (speed) {
< 50 => 'Slow',
_ => 'Fast',
};
print(label);
}SlowQuestion 3 — Solution
void main() {
Object value = 'Dart';
if (value case String s) {
print(s.toUpperCase());
}
}DARTQuestion 4 — Solution
void main() {
Object value = 12;
if (value case int n when n % 2 == 0) {
print('$n is even');
}
}12 is evenQuestion 5 — Solution
void main() {
var shelves = [('Ali', 3), ('Sara', 5)];
for (var (name, books) in shelves) {
print('$name has $books books');
}
}Ali has 3 books
Sara has 5 booksQuestion 6 — Solution
void main() {
Object item = true;
switch (item) {
case int n:
print('int $n');
case bool b:
print('bool $b');
default:
print('other');
}
}bool trueQuestion 7 — Solution
void main() {
var letter = 'e';
var kind = switch (letter) {
'a' || 'e' || 'i' || 'o' || 'u' => 'Vowel',
_ => 'Consonant',
};
print(kind);
}VowelQuestion 8 — Solution
void main() {
var (item, price, quantity) = ('Notebook', 500, 3);
print('Total: ${price * quantity}');
}Total: 1500item is unpacked but not printed; the shape still needs three variables.
Question 9 — Solution
void main() {
var accounts = [('Ali', 1200.0), ('Sara', -300.0), ('Zayan', 0.0)];
for (var (name, balance) in accounts) {
var kind = switch (balance) {
> 0 => 'credit',
< 0 => 'debit',
_ => 'empty',
};
print('$name: $kind');
}
}Ali: credit
Sara: debit
Zayan: emptyQuestion 10 — Solution
void main() {
Object input = -8;
if (input case int n when n > 0) {
print('Positive: $n');
} else if (input case int n when n < 0) {
print('Negative: $n');
} else {
print('Not a whole number');
}
}Negative: -8Each if-case creates its own n, so the same name can be reused in the else if.
Quick Revision
- What is a pattern?
- What are the two jobs a pattern can do?
- Which kind of pattern is
>= 90? And'Sunday'? - What does
case int ndo? - Write the general shape of an if-case statement.
- Name the five places where patterns can appear.
- Why must a pattern in a variable declaration always match?
- Where can a variable created by an if-case be used?
Key Takeaways
- A pattern is a shape a value is matched against and, when it fits, taken apart by.
- Matching answers yes or no; destructuring pulls values out into named variables.
>= 90,'Sunday',||and_from Lectures 024–025 were patterns all along.case int nchecks the type and binds a typed variable in one step.if (value case pattern when condition)tests a single pattern, with an optional guard.- Patterns appear in switch cases, switch arms, if-case, variable declarations, and
for-in. - In a switch or if-case a pattern may fail; in a declaration or loop it must always match.
What's Next?
Lecture 78 — Destructuring. Today destructuring appeared only with simple records. Next we will unpack records with named fields, lists, and nested values, and see how much code a single destructuring line can replace.