Watch the video (9 min)
On this page22 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is a Pattern?
  5. 2. You Have Already Used Patterns
  6. 3. Job One: Matching
  7. 4. Job Two: Destructuring
  8. 5. Both Jobs at Once: case int n
  9. 6. The if-case Statement
  10. 7. Adding a Guard with when
  11. 8. Where Patterns Appear
  12. 9. Patterns That May Fail, Patterns That Must Fit
  13. 10. A Real-World Example
  14. Code Examples
  15. Common Beginner Mistakes
  16. Best Practices
  17. Mini Challenge
  18. Practice Questions
  19. Practice Answers
  20. Quick Revision
  21. Key Takeaways
  22. What's Next?

Lecture 77 — Introduction to Patterns

Category13 — Patterns & Modern Dart
Lecture077 of 140
Estimated video length9 min
TargetDart 3.12.2
LevelIntermediate

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
  • switch statements and switch expressions, including ||, >= 90 and when
  • for-in loops
  • records such as ('Ali', 82), and reading their fields with $1 and $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:

dart
void main() {
  var result = ('Ali', 82);
  var name = result.$1;
  var marks = result.$2;
  if (marks >= 50) {
    print('$name passed');
  }
}
output
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.

dart
void main() {
  var (name, marks) = ('Ali', 82);
  print('$name has $marks marks');
}
output
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:

dart
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');
}
output
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.

dart
void main() {
  var temperature = 38.5;
  var label = switch (temperature) {
    < 0 => 'Freezing',
    >= 0 && < 25 => 'Mild',
    _ => 'Hot',
  };
  print(label);
}
output
Hot

38.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:

dart
void main() {
  var account = ('Sara', 1500.0);
  var (owner, balance) = account;
  print('Owner: $owner');
  print('Balance: $balance');
}
output
Owner: Sara
Balance: 1500.0

One 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":

dart
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');
  }
}
output
A whole number: 43

Inside 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:

dart
void main() {
  Object input = 'Zayan';
  if (input case String name) {
    print('Hello, $name');
  }
}
output
Hello, Zayan

Read 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:

dart
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');
  }
}
output
7 is a positive whole number

Change 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:

PlaceExample
switch statement casecase int n:
switch expression arm>= 90 => 'A',
if-caseif (value case int n when n > 0)
variable declarationvar (name, marks) = result;
for-in loopfor (var (name, marks) in results)

The last one destructures every item of a list as the loop reaches it:

dart
void main() {
  var results = [('Ali', 72), ('Sara', 95), ('Zayan', 88)];
  for (var (name, marks) in results) {
    print('$name scored $marks');
  }
}
output
Ali scored 72
Sara scored 95
Zayan scored 88

Lecture 050 promised that a map entry could be unpacked straight in the loop header. That is a pattern too:

dart
void main() {
  var marks = {'Ali': 72, 'Sara': 95};
  for (var MapEntry(:key, :value) in marks.entries) {
    print('$key: $value');
  }
}
output
Ali: 72
Sara: 95

MapEntry(: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:

dart
void main() {
  var (name, marks) = ('Ali', 82, 'B'); // ✗ three fields, two blanks
  print(name);
}
output
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:

dart
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');
  }
}
output
Ali: deposit of 250.0
Sara: withdrawal of -1200.0
Zayan: large deposit of 5000.0
Daily limit: 3000.0

Three 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

dart
void main() {
  var (city, temperature) = ('Lahore', 31);
  print('$city: $temperature°C');
}
output
Lahore: 31°C

Example 2 — A Constant Pattern

dart
void main() {
  var answer = 'yes';
  var reply = switch (answer) {
    'yes' => 'Confirmed',
    'no' => 'Cancelled',
    _ => 'Unknown',
  };
  print(reply);
}
output
Confirmed

Example 3 — A Relational Pattern

dart
void main() {
  var speed = 70;
  var verdict = switch (speed) {
    > 60 => 'Too fast',
    _ => 'OK',
  };
  print(verdict);
}
output
Too fast

Example 4 — Matching by Type

dart
void main() {
  Object item = 2.5;
  switch (item) {
    case int n:
      print('int $n');
    case double d:
      print('double $d');
    default:
      print('other');
  }
}
output
double 2.5

Example 5 — if-case Without a Guard

dart
void main() {
  Object nickname = 'Zee';
  if (nickname case String s) {
    print('${s.length} letters');
  }
}
output
3 letters

Example 6 — if-case With a Guard and else

dart
void main() {
  Object age = 15;
  if (age case int years when years >= 18) {
    print('Adult');
  } else {
    print('Not an adult');
  }
}
output
Not an adult

15 is an int, so the pattern matches, but the guard is false.

Example 7 — Student Marks

dart
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');
  }
}
output
Ali: F
Sara: A
Zayan: B

How it works

  1. The loop pattern (name, marks) unpacks each record.
  2. The switch tries the relational patterns from strictest to widest.
  3. 45 fails all three, so _ gives F.

Example 8 — Shopping Total

dart
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');
}
output
Notebook x2 = 1000
Pen x4 = 200
Total: 1200

How it works

  1. Each record has three fields, so the pattern has three variables.
  2. The names item, price and quantity replace $1, $2 and $3.
  3. total adds up each line as the loop runs.

Example 9 — Bank Balance Check

dart
void main() {
  Object balance = 1500;
  if (balance case int amount when amount >= 1000) {
    print('Free transfers: balance $amount');
  } else {
    print('Transfer fee applies');
  }
}
output
Free transfers: balance 1500

How it works

  1. balance is declared as Object, so Dart does not know it is a number.
  2. int amount matches because the value is an int, and names it amount.
  3. The guard amount >= 1000 is true, so the first branch runs.

Example 10 — Temperature Readings

dart
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');
  }
}
output
Mon is freezing
Tue is mild
Wed is hot

How it works

  1. The for-in pattern destructures each (day, celsius) record.
  2. >= 0 && < 25 joins two relational patterns into one range.
  3. Only values 25 and above reach _.

Common Beginner Mistakes

1. Using an if-case variable outside the if

dart
void main() {
  Object value = 'Ali';
  if (value case String s) {
    print(s);
  }
  // print(s); // ✗
}
output
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

dart
void main() {
  var limit = 50;
  var marks = 82;
  switch (marks) {
    // case limit: // ✗
    case >= 50:
      print('Passed the limit of $limit');
  }
}
output
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

dart
void main() {
  // var (name, marks) = ('Ali', 82, 'B'); // ✗
}
output
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 $1 and $2 repeatedly.
  • 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, not x).
  • 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:

output
High fever: 41.5
Sara: 37.0

Try it yourself before opening the solution.

Show solution
dart
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');
}
output
High fever: 41.5
Sara: 37.0

How it works

  1. double t matches because 41.5 is a double, and the guard t > 39 is true.
  2. 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

  1. Destructure ('Ali', 20) into name and age, and print Ali is 20.
  2. Use a switch expression with relational patterns to turn speed = 45 into Slow (below 50) or Fast. Print the result.
  3. Store Object value = 'Dart';. Use an if-case to print the text in capitals.

Application

  1. Store Object value = 12;. Use an if-case with a guard to print 12 is even only when the value is an even int.
  2. Loop over [('Ali', 3), ('Sara', 5)] with a for-in pattern and print Ali has 3 books and Sara has 5 books.
  3. Write a switch statement on Object item = true; with cases int n, bool b and default. Print bool true.
  4. Use one || pattern to print Vowel for letter = 'e' when it is 'a', 'e', 'i', 'o' or 'u', and Consonant otherwise.
  5. Destructure ('Notebook', 500, 3) into three variables and print Total: 1500.

Challenge

  1. Loop over [('Ali', 1200.0), ('Sara', -300.0), ('Zayan', 0.0)]. For each record print credit, debit, or empty using a switch expression with > 0, < 0 and _, in the form Ali: credit.
  2. Store Object input = -8;. Use one if-case for a positive int (print Positive: n) and an else if with another if-case for a negative int (print Negative: n); otherwise print Not a whole number.

Practice Answers

Question 1 — Solution

dart
void main() {
  var (name, age) = ('Ali', 20);
  print('$name is $age');
}
output
Ali is 20

Question 2 — Solution

dart
void main() {
  var speed = 45;
  var label = switch (speed) {
    < 50 => 'Slow',
    _ => 'Fast',
  };
  print(label);
}
output
Slow

Question 3 — Solution

dart
void main() {
  Object value = 'Dart';
  if (value case String s) {
    print(s.toUpperCase());
  }
}
output
DART

Question 4 — Solution

dart
void main() {
  Object value = 12;
  if (value case int n when n % 2 == 0) {
    print('$n is even');
  }
}
output
12 is even

Question 5 — Solution

dart
void main() {
  var shelves = [('Ali', 3), ('Sara', 5)];
  for (var (name, books) in shelves) {
    print('$name has $books books');
  }
}
output
Ali has 3 books
Sara has 5 books

Question 6 — Solution

dart
void main() {
  Object item = true;
  switch (item) {
    case int n:
      print('int $n');
    case bool b:
      print('bool $b');
    default:
      print('other');
  }
}
output
bool true

Question 7 — Solution

dart
void main() {
  var letter = 'e';
  var kind = switch (letter) {
    'a' || 'e' || 'i' || 'o' || 'u' => 'Vowel',
    _ => 'Consonant',
  };
  print(kind);
}
output
Vowel

Question 8 — Solution

dart
void main() {
  var (item, price, quantity) = ('Notebook', 500, 3);
  print('Total: ${price * quantity}');
}
output
Total: 1500

item is unpacked but not printed; the shape still needs three variables.

Question 9 — Solution

dart
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');
  }
}
output
Ali: credit
Sara: debit
Zayan: empty

Question 10 — Solution

dart
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');
  }
}
output
Negative: -8

Each if-case creates its own n, so the same name can be reused in the else if.


Quick Revision

  1. What is a pattern?
  2. What are the two jobs a pattern can do?
  3. Which kind of pattern is >= 90? And 'Sunday'?
  4. What does case int n do?
  5. Write the general shape of an if-case statement.
  6. Name the five places where patterns can appear.
  7. Why must a pattern in a variable declaration always match?
  8. 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 n checks 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.