Collections: Lists
Introduction to Lists in Dart
A List is one of the most commonly used collection types in Dart. It represents an ordered group of elements that can be accessed by their index position. Lists in Dart are similar to arrays in other programming languages, but they come with a rich set of built-in methods that make them extremely powerful and flexible.
List<T> class, where T is the type of elements the list holds. Dart does not have a separate "array" type — lists are the go-to ordered collection.Creating Lists
There are several ways to create lists in Dart. Let's explore each approach.
List Literals (Growable)
The most common way to create a list is using square bracket syntax. By default, these lists are growable, meaning you can add or remove elements after creation.
Creating Growable Lists
void main() {
// Type is inferred as List<int>
var numbers = [1, 2, 3, 4, 5];
print(numbers); // [1, 2, 3, 4, 5]
// Explicit type annotation
List<String> fruits = ['Apple', 'Banana', 'Cherry'];
print(fruits); // [Apple, Banana, Cherry]
// Empty list with type annotation
List<double> prices = [];
prices.add(9.99);
prices.add(14.50);
print(prices); // [9.99, 14.5]
// Mixed types using dynamic (not recommended)
List<dynamic> mixed = [1, 'hello', true, 3.14];
print(mixed); // [1, hello, true, 3.14]
}
Fixed-Length Lists
You can create a list with a fixed size using List.filled(). Fixed-length lists cannot grow or shrink — you can only change the values at existing positions.
Fixed-Length Lists
void main() {
// Create a fixed list of 5 elements, all initialized to 0
var fixedList = List<int>.filled(5, 0);
print(fixedList); // [0, 0, 0, 0, 0]
// Modify elements by index
fixedList[0] = 10;
fixedList[1] = 20;
fixedList[4] = 50;
print(fixedList); // [10, 20, 0, 0, 50]
// fixedList.add(60); // ERROR! Cannot add to a fixed-length list
// fixedList.removeAt(0); // ERROR! Cannot remove from a fixed-length list
// Fixed list of Strings
var names = List<String>.filled(3, '');
names[0] = 'Ahmed';
names[1] = 'Sara';
names[2] = 'Omar';
print(names); // [Ahmed, Sara, Omar]
}
List(n) constructor for creating fixed-length lists is deprecated in Dart 2. Always use List.filled(length, defaultValue) or List.generate() instead.Accessing Elements by Index
List elements are accessed using zero-based indexing. The first element is at index 0, the second at index 1, and so on.
Index-Based Access
void main() {
var colors = ['Red', 'Green', 'Blue', 'Yellow', 'Purple'];
print(colors[0]); // Red (first element)
print(colors[2]); // Blue (third element)
print(colors[4]); // Purple (last element)
// Access the first and last elements
print(colors.first); // Red
print(colors.last); // Purple
// Modify an element
colors[1] = 'Lime';
print(colors); // [Red, Lime, Blue, Yellow, Purple]
// Get the length
print(colors.length); // 5
// Access last element using length
print(colors[colors.length - 1]); // Purple
}
colors[10] on a 5-element list) will throw a RangeError at runtime. Always check the list length before accessing by index if you are unsure.Adding Elements
Growable lists support several methods for adding new elements.
Adding Elements to a List
void main() {
var languages = ['Dart', 'Python'];
// add() - Adds a single element to the end
languages.add('JavaScript');
print(languages); // [Dart, Python, JavaScript]
// addAll() - Adds all elements from another iterable
languages.addAll(['Go', 'Rust']);
print(languages); // [Dart, Python, JavaScript, Go, Rust]
// insert() - Inserts at a specific index
languages.insert(1, 'Java');
print(languages); // [Dart, Java, Python, JavaScript, Go, Rust]
// insertAll() - Inserts multiple elements at a specific index
languages.insertAll(3, ['C++', 'Swift']);
print(languages);
// [Dart, Java, Python, C++, Swift, JavaScript, Go, Rust]
}
Removing Elements
There are multiple ways to remove elements from a growable list.
Removing Elements from a List
void main() {
var items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
// remove() - Removes the first occurrence of a value
items.remove('Cherry');
print(items); // [Apple, Banana, Date, Elderberry]
// removeAt() - Removes element at a specific index
var removed = items.removeAt(0);
print(removed); // Apple
print(items); // [Banana, Date, Elderberry]
// removeLast() - Removes and returns the last element
var last = items.removeLast();
print(last); // Elderberry
print(items); // [Banana, Date]
// removeRange() - Removes a range of elements
var numbers = [1, 2, 3, 4, 5, 6, 7];
numbers.removeRange(2, 5); // Remove index 2 to 4
print(numbers); // [1, 2, 6, 7]
// removeWhere() - Removes elements matching a condition
var scores = [85, 42, 91, 37, 78, 55];
scores.removeWhere((score) => score < 50);
print(scores); // [85, 91, 78, 55]
// clear() - Removes all elements
scores.clear();
print(scores); // []
}
Essential List Methods
Dart lists come with a wide variety of methods. Here are the most important ones you will use regularly.
Searching and Checking
Search Methods
void main() {
var fruits = ['Apple', 'Banana', 'Cherry', 'Apple', 'Date'];
// contains() - Check if an element exists
print(fruits.contains('Banana')); // true
print(fruits.contains('Mango')); // false
// indexOf() - Find the first index of an element
print(fruits.indexOf('Apple')); // 0
print(fruits.indexOf('Mango')); // -1 (not found)
// lastIndexOf() - Find the last index of an element
print(fruits.lastIndexOf('Apple')); // 3
// isEmpty and isNotEmpty
print(fruits.isEmpty); // false
print(fruits.isNotEmpty); // true
// length
print(fruits.length); // 5
// any() - Check if any element matches a condition
print(fruits.any((f) => f.startsWith('C'))); // true
// every() - Check if ALL elements match a condition
print(fruits.every((f) => f.length > 2)); // true
}
Sorting
Sorting Lists
void main() {
// sort() - Sorts in place (modifies the original list)
var numbers = [5, 2, 8, 1, 9, 3];
numbers.sort();
print(numbers); // [1, 2, 3, 5, 8, 9]
// Sort strings alphabetically
var names = ['Charlie', 'Alice', 'Bob', 'David'];
names.sort();
print(names); // [Alice, Bob, Charlie, David]
// Custom sort with comparator
var words = ['banana', 'apple', 'cherry', 'date'];
words.sort((a, b) => a.length.compareTo(b.length));
print(words); // [date, apple, banana, cherry]
// Sort descending
var scores = [75, 92, 88, 64, 95];
scores.sort((a, b) => b.compareTo(a));
print(scores); // [95, 92, 88, 75, 64]
}
Reversed and Sublist
Reversed and Sublist
void main() {
var letters = ['A', 'B', 'C', 'D', 'E'];
// reversed - Returns an Iterable (not a List)
var reversedLetters = letters.reversed;
print(reversedLetters); // (E, D, C, B, A)
// Convert reversed Iterable to List
var reversedList = letters.reversed.toList();
print(reversedList); // [E, D, C, B, A]
// sublist() - Extract a portion of the list
var subset1 = letters.sublist(1, 4); // From index 1 to 3
print(subset1); // [B, C, D]
var subset2 = letters.sublist(2); // From index 2 to end
print(subset2); // [C, D, E]
// Note: sublist creates a NEW list (not a view)
subset1[0] = 'Z';
print(letters); // [A, B, C, D, E] (unchanged)
}
Iterating Over Lists
Dart provides several ways to loop through list elements.
Iteration Methods
void main() {
var cities = ['Riyadh', 'Dubai', 'Cairo', 'Istanbul'];
// 1. for-in loop (most common)
for (var city in cities) {
print(city);
}
// 2. Traditional for loop (when you need the index)
for (int i = 0; i < cities.length; i++) {
print('$i: ${cities[i]}');
}
// 3. forEach method
cities.forEach((city) {
print('City: $city');
});
// 4. forEach with tear-off
cities.forEach(print);
// 5. Using asMap() to get index and value
cities.asMap().forEach((index, city) {
print('$index: $city');
});
// 6. Using indexed (Dart 3+)
for (var (index, city) in cities.indexed) {
print('$index: $city');
}
}
for-in when you only need the values. Use a traditional for loop when you need the index. Use forEach for simple one-line operations. Avoid forEach when you need to use break or return — it does not support them.The Spread Operator (...)
The spread operator ... allows you to unpack all elements of a list into another list. It is incredibly useful for combining lists.
Spread Operator
void main() {
var first = [1, 2, 3];
var second = [4, 5, 6];
// Combine two lists
var combined = [...first, ...second];
print(combined); // [1, 2, 3, 4, 5, 6]
// Add elements before, between, or after
var full = [0, ...first, 99, ...second, 100];
print(full); // [0, 1, 2, 3, 99, 4, 5, 6, 100]
// Null-aware spread operator (...?)
List<int>? maybeNull;
var safe = [1, 2, ...?maybeNull, 3];
print(safe); // [1, 2, 3]
// Useful for conditionally including lists
maybeNull = [10, 20];
var withValues = [1, 2, ...?maybeNull, 3];
print(withValues); // [1, 2, 10, 20, 3]
}
Collection if and Collection for
Dart supports conditional elements and loops directly inside list literals. These are called collection if and collection for, and they make list building very expressive.
Collection if
Conditional Elements in Lists
void main() {
bool isLoggedIn = true;
bool isAdmin = false;
var menu = [
'Home',
'About',
if (isLoggedIn) 'Profile',
if (isLoggedIn) 'Settings',
if (isAdmin) 'Admin Panel',
'Contact',
];
print(menu); // [Home, About, Profile, Settings, Contact]
// Collection if-else
var greeting = [
if (isLoggedIn) 'Welcome back!' else 'Please log in',
];
print(greeting); // [Welcome back!]
}
Collection for
Loop Elements in Lists
void main() {
// Generate a list of squared numbers
var squares = [
for (int i = 1; i <= 5; i++) i * i,
];
print(squares); // [1, 4, 9, 16, 25]
// Transform one list into another
var names = ['alice', 'bob', 'charlie'];
var uppercased = [
for (var name in names) name.toUpperCase(),
];
print(uppercased); // [ALICE, BOB, CHARLIE]
// Combine collection if and for
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenSquares = [
for (var n in numbers)
if (n.isEven) n * n,
];
print(evenSquares); // [4, 16, 36, 64, 100]
}
Immutable Lists
Sometimes you want a list that cannot be modified after creation. Dart provides several ways to create immutable (read-only) lists.
Creating Immutable Lists
void main() {
// Using const keyword - compile-time constant
const colors = ['Red', 'Green', 'Blue'];
// colors.add('Yellow'); // ERROR! Cannot modify a const list
// colors[0] = 'Pink'; // ERROR! Cannot modify a const list
// const list assigned to a var variable
var moreColors = const ['Cyan', 'Magenta', 'Yellow'];
// moreColors.add('Black'); // ERROR at runtime!
// But you CAN reassign the variable itself
moreColors = ['Black', 'White']; // OK - new growable list
moreColors.add('Gray'); // OK - this list is growable
// List.unmodifiable() - runtime immutable from any iterable
var original = [1, 2, 3, 4, 5];
var unmodifiable = List<int>.unmodifiable(original);
// unmodifiable.add(6); // ERROR! Unsupported operation
// unmodifiable[0] = 99; // ERROR! Unsupported operation
// Original list is still modifiable
original.add(6);
print(original); // [1, 2, 3, 4, 5, 6]
print(unmodifiable); // [1, 2, 3, 4, 5] (snapshot at creation time)
}
const lists are compile-time constants and are deeply immutable. List.unmodifiable() creates a runtime unmodifiable view. Use const when the values are known at compile time, and List.unmodifiable() when you want to freeze a dynamically built list.List.generate()
The List.generate() constructor creates a list of a given length with values computed by a function. It is very handy for initializing lists with patterns.
Generating Lists
void main() {
// Generate a list of 5 elements: index * 2
var doubles = List<int>.generate(5, (index) => index * 2);
print(doubles); // [0, 2, 4, 6, 8]
// Generate multiplication table for 7
var table = List<String>.generate(
10,
(i) => '7 x ${i + 1} = ${7 * (i + 1)}',
);
for (var row in table) {
print(row);
}
// 7 x 1 = 7
// 7 x 2 = 14
// ... and so on
// Generate a list of empty lists (2D structure)
var grid = List<List<int>>.generate(3, (_) => []);
grid[0].addAll([1, 2, 3]);
grid[1].addAll([4, 5, 6]);
grid[2].addAll([7, 8, 9]);
print(grid); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// Fixed-length generated list
var fixed = List<int>.generate(5, (i) => i * 10, growable: false);
print(fixed); // [0, 10, 20, 30, 40]
// fixed.add(50); // ERROR! Cannot add to a fixed-length list
}
Common Patterns: Filtering, Mapping, Reducing
Dart lists support powerful functional-style operations that let you transform data without writing explicit loops.
Filtering with where()
Filtering Lists
void main() {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// where() returns a lazy Iterable - convert to List with toList()
var evens = numbers.where((n) => n.isEven).toList();
print(evens); // [2, 4, 6, 8, 10]
var greaterThan5 = numbers.where((n) => n > 5).toList();
print(greaterThan5); // [6, 7, 8, 9, 10]
// firstWhere() - Find first matching element
var firstEven = numbers.firstWhere((n) => n.isEven);
print(firstEven); // 2
// firstWhere with orElse for safety
var bigNumber = numbers.firstWhere(
(n) => n > 100,
orElse: () => -1,
);
print(bigNumber); // -1
}
Mapping with map()
Transforming Lists
void main() {
var numbers = [1, 2, 3, 4, 5];
// map() transforms each element
var doubled = numbers.map((n) => n * 2).toList();
print(doubled); // [2, 4, 6, 8, 10]
var names = ['alice', 'bob', 'charlie'];
var capitalized = names.map((name) {
return name[0].toUpperCase() + name.substring(1);
}).toList();
print(capitalized); // [Alice, Bob, Charlie]
// Chaining where and map
var students = [
{'name': 'Ahmed', 'grade': 92},
{'name': 'Sara', 'grade': 45},
{'name': 'Omar', 'grade': 88},
{'name': 'Layla', 'grade': 37},
];
var passingNames = students
.where((s) => (s['grade'] as int) >= 50)
.map((s) => s['name'])
.toList();
print(passingNames); // [Ahmed, Omar]
}
Reducing with reduce() and fold()
Reducing Lists to a Single Value
void main() {
var numbers = [10, 20, 30, 40, 50];
// reduce() - Combines all elements into one value
var sum = numbers.reduce((a, b) => a + b);
print(sum); // 150
var max = numbers.reduce((a, b) => a > b ? a : b);
print(max); // 50
// fold() - Like reduce but with an initial value
// Safer for empty lists since reduce throws on empty lists
var total = numbers.fold<int>(0, (sum, n) => sum + n);
print(total); // 150
// fold with a different return type
var words = ['Hello', 'Dart', 'World'];
var sentence = words.fold<String>('', (result, word) {
return result.isEmpty ? word : '$result $word';
});
print(sentence); // Hello Dart World
// join() - Simpler way to concatenate strings
var joined = words.join(' ');
print(joined); // Hello Dart World
// Calculate average
var scores = [85, 90, 78, 92, 88];
var average = scores.fold<double>(0, (sum, s) => sum + s) / scores.length;
print(average); // 86.6
}
fold() over reduce() when the list might be empty. reduce() throws a StateError on an empty list, while fold() simply returns the initial value. Also use fold() when the return type differs from the element type.Useful List Tricks
Practical List Operations
void main() {
// Remove duplicates using toSet()
var withDuplicates = [1, 2, 3, 2, 4, 1, 5, 3];
var unique = withDuplicates.toSet().toList();
print(unique); // [1, 2, 3, 4, 5]
// Flatten a list of lists using expand()
var nested = [[1, 2], [3, 4], [5, 6]];
var flat = nested.expand((list) => list).toList();
print(flat); // [1, 2, 3, 4, 5, 6]
// Take and skip
var items = ['a', 'b', 'c', 'd', 'e', 'f'];
print(items.take(3).toList()); // [a, b, c]
print(items.skip(3).toList()); // [d, e, f]
// Swap two elements
var list = [10, 20, 30, 40];
var temp = list[0];
list[0] = list[3];
list[3] = temp;
print(list); // [40, 20, 30, 10]
// Copy a list (shallow copy)
var original = [1, 2, 3];
var copy = [...original]; // or List.from(original) or .toList()
copy.add(4);
print(original); // [1, 2, 3]
print(copy); // [1, 2, 3, 4]
// Check list equality (element by element)
// import 'package:collection/collection.dart';
// ListEquality().equals([1, 2], [1, 2]); // true
}
Practice Exercise
Create a Dart program that manages a shopping list. Your program should:
- Create a list of 5 grocery items.
- Add 2 more items to the end of the list.
- Insert 1 item at the beginning (index 0).
- Remove the third item from the list.
- Sort the list alphabetically.
- Use
map()to create a new list where each item is prefixed with a number (e.g., "1. Milk", "2. Eggs"). - Print the final numbered list.
- Use
where()to filter items that start with a specific letter and print the result.
Try using collection if to conditionally add a "Discount Coupon" item only if the list has more than 5 items.