Watch the video (10 min)
On this page26 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Three Questions You Can Ask a List
  5. 2. contains — Is the Value There?
  6. 3. indexOf With a Start Index
  7. 4. lastIndexOf — Search From the End
  8. 5. indexWhere — Position of the First Match
  9. 6. lastIndexWhere — Position of the Last Match
  10. 7. firstWhere — The First Matching Element
  11. 8. When Nothing Matches
  12. 9. orElse — A Fallback When Nothing Matches
  13. 10. lastWhere — The Last Matching Element
  14. 11. singleWhere — Exactly One Match
  15. 12. any and every — Questions About the Whole List
  16. 13. Choosing the Right Method
  17. 14. A Real-World Example
  18. Code Examples
  19. Common Beginner Mistakes
  20. Best Practices
  21. Mini Challenge
  22. Practice Questions
  23. Practice Answers
  24. Quick Revision
  25. Key Takeaways
  26. What's Next?

Lecture 42 — List Searching

Category7 — Lists
Lecture042 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelBeginner

Until now, finding something in a list meant writing a loop and a counter. Dart's search methods answer "is it there?", "where is it?" and "which one is it?" in a single line.

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 001–041:

  • nullable types and ?? (Lectures 013–014)
  • for-in loops (Lecture 027)
  • arrow and anonymous functions (Lecture 034)
  • creating lists, length, and isEmpty (Lecture 039)
  • indexes, indexOf, and the -1 result (Lecture 040)
  • removeWhere and passing a function to a list method (Lecture 041)

Connection With Previous Lectures

In Lecture 041 the cart grew and shrank. Now we want to ask it questions: Is there milk in the cart? Where is the first failing mark? Which student scored above 90? With what we know, each question needs a loop:

dart
var found = false;
for (final item in cart) {
  if (item == 'Milk') found = true;
}

Four lines and a variable for a yes/no answer. In Lecture 040 indexOf gave us a position, but only for an exact value — it cannot find "the first mark below 50". This lecture gives us one method for each kind of question.


Learning Objectives

By the end of this lecture you will be able to:

  • Check whether a value is in a list with contains
  • Find positions with indexOf and lastIndexOf, including a start index
  • Find the position of the first or last element matching a condition with indexWhere and lastIndexWhere
  • Get the matching element itself with firstWhere and lastWhere
  • Recognize the Bad state: No element error and prevent it with orElse
  • Use singleWhere when exactly one element should match
  • Test a condition across the whole list with any and every
  • Choose the right search method for the question you are asking

1. Three Questions You Can Ask a List

Every search answers one of three questions: is it there? (a bool), where is it? (an int index) or which one is it? (the element).

Analogy — think of a class register. "Is Sara here?" gets a yes or no. "Which row is she in?" gets a number. "Who is the first student with full marks?" gets a name.

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  print(names.contains('Sara'));
  print(names.indexOf('Sara'));
  print(names.firstWhere((name) => name.length > 4));
}
output
true
1
Zayan

2. contains — Is the Value There?

contains(value) returns true if any element equals the value.

dart
void main() {
  var cart = ['Milk', 'Bread', 'Eggs'];
  print(cart.contains('Milk'));
  print(cart.contains('milk'));
}
output
true
false

The comparison is exact: 'milk' with a small m is a different string.


3. indexOf With a Start Index

Lecture 040 showed that indexOf returns the first matching index, or -1. An optional second argument says where to start looking:

dart
void main() {
  var marks = [70, 85, 70, 92, 70];
  print(marks.indexOf(70));
  print(marks.indexOf(70, 1));
  print(marks.indexOf(70, 3));
}
output
0
2
4

Starting at index 1 skips the first 70, so the next one is found at 2.


4. lastIndexOf — Search From the End

lastIndexOf returns the last matching index. Its optional start index is where the search begins going backwards.

dart
void main() {
  var marks = [70, 85, 70, 92, 70];
  print(marks.lastIndexOf(70));
  print(marks.lastIndexOf(70, 3));
  print(marks.lastIndexOf(100));
}
output
4
2
-1

5. indexWhere — Position of the First Match

indexOf needs the exact value. indexWhere takes a function instead, like removeWhere in Lecture 041, and returns the index of the first element for which it returns true.

dart
void main() {
  var marks = [85, 92, 42, 78, 39];
  print(marks.indexWhere((mark) => mark < 50));
  print(marks.indexWhere((mark) => mark > 95));
}
output
2
-1

No match gives -1, just like indexOf. It also accepts a start index: marks.indexWhere((mark) => mark < 50, 3) returns 4.


6. lastIndexWhere — Position of the Last Match

dart
void main() {
  var temps = [18.0, 31.5, 22.0, 33.0, 25.5];
  print(temps.lastIndexWhere((t) => t > 30));
}
output
3

The last day above 30 degrees is at index 3.


7. firstWhere — The First Matching Element

Often we want the element, not its position. firstWhere returns the first element for which the function returns true.

dart
void main() {
  var prices = [450, 1200, 300, 2500];
  var expensive = prices.firstWhere((price) => price > 1000);
  print(expensive);
}
output
1200

8. When Nothing Matches

indexWhere can return -1, but firstWhere has to return an element — and there is none. So the program stops:

dart
void main() {
  var prices = [450, 1200, 300];
  var huge = prices.firstWhere((price) => price > 5000); // ✗ no match
  print(huge);
}
output
Unhandled exception:
Bad state: No element

This is a StateError: the list was not in a state where an answer exists. We will learn to catch errors like this in Lecture 084 — try, catch & finally. For now, we prevent it.


9. orElse — A Fallback When Nothing Matches

firstWhere has a named argument orElse: a function whose result is returned when no element matches.

dart
void main() {
  var prices = [450, 1200, 300];
  var huge = prices.firstWhere((price) => price > 5000, orElse: () => 0);
  print(huge);
}
output
0

The fallback must have the list's type. For a List<int> it must be an int, so pick a value that cannot be a real answer, such as 0 or -1.


10. lastWhere — The Last Matching Element

lastWhere works like firstWhere but searches from the end. It takes orElse too.

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan', 'Sana'];
  print(names.lastWhere((name) => name.startsWith('S')));
  print(names.lastWhere((name) => name.startsWith('X'), orElse: () => 'none'));
}
output
Sana
none

11. singleWhere — Exactly One Match

singleWhere is for when exactly one element should match, such as an ID. It fails if there are none — or if there are several:

dart
void main() {
  var marks = [85, 42, 78];
  print(marks.singleWhere((mark) => mark < 50));
  print(marks.singleWhere((mark) => mark > 50)); // ✗ two matches
}
output
42
Unhandled exception:
Bad state: Too many elements

Use it only when "more than one" really means something is wrong. Otherwise use firstWhere.


12. any and every — Questions About the Whole List

any returns true if at least one element passes the test. every returns true if all of them do.

dart
void main() {
  var marks = [85, 92, 42, 78];
  print(marks.any((mark) => mark < 50));
  print(marks.every((mark) => mark >= 50));
  print(marks.every((mark) => mark > 0));
}
output
true
false
true

any stops at the first true, every at the first false. On an empty list, any is false (nothing passed) and every is true (nothing failed).


13. Choosing the Right Method

QuestionMethodReturnsWhen nothing matches
Is this value there?containsboolfalse
Does any / every element pass?any / everyboolfalse / true
Where is this value?indexOf / lastIndexOfint-1
Where is the first / last match?indexWhere / lastIndexWhereint-1
Which element matches?firstWhere / lastWhereelementerror, or orElse
Which one element matches?singleWhereelementerror, or orElse

Dart can also collect every matching element into a new collection with where. We will study it in Lecture 043 — List Transformation.


14. A Real-World Example

dart
void main() {
  final students = ['Ali', 'Sara', 'Zayan', 'Hina'];
  final marks = [72, 95, 45, 88];

  print('Hina enrolled: ${students.contains('Hina')}');
  print('Everyone passed: ${marks.every((mark) => mark >= 50)}');

  var failing = marks.indexWhere((mark) => mark < 50);
  if (failing != -1) {
    print('Needs help: ${students[failing]}');
  }

  var top = marks.firstWhere((mark) => mark > 90, orElse: () => 0);
  print('First mark above 90: $top by ${students[marks.indexOf(top)]}');
}
output
Hina enrolled: true
Everyone passed: false
Needs help: Zayan
First mark above 90: 95 by Sara

contains and every answer yes/no questions. indexWhere gives a position we can use in the second list, after checking for -1. firstWhere finds the mark and indexOf turns it back into a position.


Code Examples

Example 1 — Checking Membership

dart
void main() {
  var guests = ['Ali', 'Sara', 'Zayan'];
  print(guests.contains('Zayan'));
  print(guests.contains('Omar'));
}
output
true
false

Example 2 — Every Position of a Value

dart
void main() {
  var letters = ['a', 'b', 'a', 'c', 'a'];
  var position = letters.indexOf('a');
  while (position != -1) {
    print(position);
    position = letters.indexOf('a', position + 1);
  }
}
output
0
2
4

Each search starts one place after the previous match, until indexOf returns -1.

Example 3 — First and Last Occurrence

dart
void main() {
  var visits = ['Ali', 'Sara', 'Ali', 'Zayan'];
  print('First visit: ${visits.indexOf('Ali')}');
  print('Last visit: ${visits.lastIndexOf('Ali')}');
}
output
First visit: 0
Last visit: 2

Example 4 — First Even Number

dart
void main() {
  var numbers = [7, 13, 8, 21, 4];
  print(numbers.indexWhere((n) => n % 2 == 0));
  print(numbers.firstWhere((n) => n % 2 == 0));
}
output
2
8

The same test gives the position with indexWhere and the element with firstWhere.

Example 5 — A Safe Lookup

dart
void main() {
  var names = ['Ali', 'Sara'];
  var match = names.firstWhere(
    (name) => name.startsWith('Z'),
    orElse: () => 'nobody',
  );
  print('Found: $match');
}
output
Found: nobody

Example 6 — any and every on Words

dart
void main() {
  var words = ['dart', 'list', 'search'];
  print(words.any((word) => word.length > 5));
  print(words.every((word) => word.length > 5));
}
output
true
false

Example 7 — Student Marks: Who Failed First?

dart
void main() {
  final students = ['Ali', 'Sara', 'Zayan'];
  final marks = [78, 41, 35];
  var first = marks.indexWhere((mark) => mark < 50);
  var last = marks.lastIndexWhere((mark) => mark < 50);
  print('First below 50: ${students[first]}');
  print('Last below 50: ${students[last]}');
}
output
First below 50: Sara
Last below 50: Zayan

How it works

  1. Both searches use the same test, from opposite ends.
  2. The index found in marks is used to read the matching name from students.

Example 8 — Shopping: Is Anything Over Budget?

dart
void main() {
  final prices = [450, 1200, 300, 800];
  const budget = 1000;
  if (prices.any((price) => price > budget)) {
    var item = prices.firstWhere((price) => price > budget);
    print('Over budget: $item');
  } else {
    print('All items fit the budget');
  }
}
output
Over budget: 1200

How it works

  1. any checks first, so firstWhere only runs when a match is guaranteed.
  2. Inside the function, budget is read from main — a closure, as in Lecture 037.

Example 9 — Bank Balance: The Last Withdrawal

dart
void main() {
  final transactions = [500, -200, 1000, -50, 300];
  var lastOut = transactions.lastWhere((amount) => amount < 0, orElse: () => 0);
  var allSmall = transactions.every((amount) => amount > -1000);
  print('Last withdrawal: $lastOut');
  print('No big withdrawals: $allSmall');
}
output
Last withdrawal: -50
No big withdrawals: true

How it works

  1. Withdrawals are negative, so amount < 0 finds them; lastWhere picks the most recent.
  2. orElse: () => 0 means "no withdrawals" instead of a crash.

Example 10 — Temperature: First Hot Day

dart
void main() {
  final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  final temps = [24.5, 28.0, 35.5, 31.0, 36.5];
  var hot = temps.indexWhere((t) => t > 35);
  if (hot == -1) {
    print('No hot days');
  } else {
    print('First hot day: ${days[hot]} (${temps[hot]}°C)');
  }
}
output
First hot day: Wed (35.5°C)

How it works

  1. indexWhere returns 2, the position of 35.5.
  2. The -1 check keeps days[hot] from ever being days[-1].

Common Beginner Mistakes

1. Calling firstWhere without orElse when there may be no match

dart
var huge = prices.firstWhere((price) => price > 5000);
output
Unhandled exception:
Bad state: No element

Add orElse, or check with any first.

2. Returning null from orElse

dart
void main() {
  var names = ['Ali', 'Sara'];
  // String? found = names.firstWhere(
  //   (name) => name.startsWith('Z'),
  //   orElse: () => null, // ✗ must be a String
  // );
}
output
Error: A value of type 'Null' can't be returned from a function with return type 'String'.

orElse must return the list's type. Use a fallback like 'none' — or use indexWhere and check for -1.

3. Passing a value where a function is expected

dart
void main() {
  var marks = [85, 42, 78];
  // var low = marks.indexWhere(50); // ✗ needs a function
}
output
Error: The argument type 'int' can't be assigned to the parameter type 'bool Function(int)'.

Write the test as a function: marks.indexWhere((mark) => mark == 50) — or use indexOf(50).

4. Treating indexOf as a yes/no answer

dart
void main() {
  var marks = [85, 42, 78];
  // if (marks.indexOf(42)) print('found'); // ✗ int, not bool
}
output
Error: A value of type 'int' can't be assigned to a variable of type 'bool'.

Use contains(42), or indexOf(42) != -1.

5. Searching with the wrong type

dart
void main() {
  var marks = [85, 42, 78];
  print(marks.contains('85'));
}
output
false

No error — the string '85' simply never equals the number 85. Make sure the value you search for has the list's type.

6. Using singleWhere when duplicates are allowed

If two elements can match, singleWhere stops with Bad state: Too many elements. Use firstWhere.


Best Practices

  • Use contains for a yes/no question about a value, any / every for a condition.
  • Check indexOf and indexWhere results for -1 before using them as an index.
  • Give firstWhere and lastWhere an orElse unless a match is guaranteed.
  • Choose an orElse value that cannot be confused with a real result.
  • Prefer a search method over a hand-written loop with a flag variable.
  • Reserve singleWhere for values that must be unique.

Mini Challenge

Given final marks = [64, 88, 47, 95, 71];, print whether any mark is below 50, the position of the first mark above 90, and the last mark between 60 and 80.

output
Any below 50: true
First above 90 at: 3
Last in 60–80: 71

Try it yourself before opening the solution.

Show solution
dart
void main() {
  final marks = [64, 88, 47, 95, 71];
  print('Any below 50: ${marks.any((mark) => mark < 50)}');
  print('First above 90 at: ${marks.indexWhere((mark) => mark > 90)}');
  var middle = marks.lastWhere((mark) => mark >= 60 && mark <= 80);
  print('Last in 60–80: $middle');
}
output
Any below 50: true
First above 90 at: 3
Last in 60–80: 71

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Given ['red', 'green', 'blue'], print whether it contains 'green' and whether it contains 'pink'.
  2. Given [3, 7, 3, 9, 3], print the first and the last index of 3.
  3. Given [12, 5, 30, 8], print the first element greater than 10.

Application

  1. Given [3, 7, 3, 9, 3], print the index of the first 3 after index 0.
  2. Given [15, 22, 8, 40, 5], print the index of the last number smaller than 10.
  3. Given ['Ali', 'Sara', 'Zayan'], find the first name longer than 6 letters, printing 'none' if there is no such name.
  4. Given [72, 65, 88, 91], print whether every mark is at least 60 and whether any mark is above 90.
  5. Given [101, 205, 307] (product IDs), use singleWhere to print the ID that starts with 2 when written as text. Hint: '$id'.startsWith('2').

Challenge

  1. Given final items = ['Milk', 'Bread', 'Eggs'] and final prices = [180, 120, 300], find the first price above 150 and print the item's name and price as Milk: 180. If no price matches, print Nothing above 150.
  2. Given final temps = [21.0, 19.5, -3.0, 25.0, -1.5], print the index of the first reading below 0, the last reading below 0, and whether all readings are below 30.

Practice Answers

Question 1 — Solution

dart
void main() {
  var colors = ['red', 'green', 'blue'];
  print(colors.contains('green'));
  print(colors.contains('pink'));
}
output
true
false

Question 2 — Solution

dart
void main() {
  var numbers = [3, 7, 3, 9, 3];
  print(numbers.indexOf(3));
  print(numbers.lastIndexOf(3));
}
output
0
4

Question 3 — Solution

dart
void main() {
  var numbers = [12, 5, 30, 8];
  print(numbers.firstWhere((n) => n > 10));
}
output
12

Question 4 — Solution

dart
void main() {
  var numbers = [3, 7, 3, 9, 3];
  print(numbers.indexOf(3, 1));
}
output
2

Question 5 — Solution

dart
void main() {
  var numbers = [15, 22, 8, 40, 5];
  print(numbers.lastIndexWhere((n) => n < 10));
}
output
4

Question 6 — Solution

dart
void main() {
  var names = ['Ali', 'Sara', 'Zayan'];
  var long = names.firstWhere((name) => name.length > 6, orElse: () => 'none');
  print(long);
}
output
none

Question 7 — Solution

dart
void main() {
  var marks = [72, 65, 88, 91];
  print(marks.every((mark) => mark >= 60));
  print(marks.any((mark) => mark > 90));
}
output
true
true

Question 8 — Solution

dart
void main() {
  var ids = [101, 205, 307];
  print(ids.singleWhere((id) => '$id'.startsWith('2')));
}
output
205

Question 9 — Solution

dart
void main() {
  final items = ['Milk', 'Bread', 'Eggs'];
  final prices = [180, 120, 300];
  var index = prices.indexWhere((price) => price > 150);
  if (index == -1) {
    print('Nothing above 150');
  } else {
    print('${items[index]}: ${prices[index]}');
  }
}
output
Milk: 180

indexWhere is the right tool here: the position lets us read both lists.

Question 10 — Solution

dart
void main() {
  final temps = [21.0, 19.5, -3.0, 25.0, -1.5];
  print(temps.indexWhere((t) => t < 0));
  print(temps.lastWhere((t) => t < 0));
  print(temps.every((t) => t < 30));
}
output
2
-1.5
true

The question asks for the first index but the last reading, so one line uses indexWhere and the other lastWhere.


Quick Revision

  1. Which method answers "is this value in the list?"
  2. What does the second argument of indexOf do?
  3. What is the difference between indexOf and indexWhere?
  4. What do indexWhere and firstWhere give back when nothing matches?
  5. What does orElse do, and what type must it return?
  6. When does singleWhere fail?
  7. What do any and every return on an empty list?

Key Takeaways

  • contains(value) answers "is it there?" with a bool.
  • indexOf / lastIndexOf find an exact value's first / last index, or -1; both accept a start index.
  • indexWhere / lastIndexWhere find the index of the first / last element passing a test, or -1.
  • firstWhere / lastWhere return the matching element.
  • With no match and no orElse, they stop the program with Bad state: No element.
  • orElse supplies a fallback of the list's own type.
  • singleWhere expects exactly one match and fails on zero or several.
  • any is true if at least one element passes; every is true if all do.

What's Next?

Lecture 43 — List Transformation. Searching finds one element or one position. Next we build new lists from old ones: collecting every match with where, changing every element with map, and more.


Video lecture

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