Dart List
last modified October 23, 2026
A List in Dart is an ordered collection of elements that can be accessed by
index. Elements keep their insertion order, and the same value may appear
more than once. The type is written List<E>, where
E is the element type. Dart lists are zero-indexed, and similar
to arrays in other languages.
A list is either fixed-length or growable. A fixed-length list is created
with a given size and cannot grow or shrink. A growable list can be extended
with add, insert and the other mutating methods.
List literals create growable lists.
Creating a List
The simplest way to create a list is a list literal in square brackets. The
List constructors create fixed-length lists, and
List.generate computes each element from its index.
void main() {
// List literal creates a growable list
var fruits = ['apple', 'banana', 'orange'];
print(fruits);
// Fixed-length list created with the List constructor
var numbers = List<int>.filled(3, 0);
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
print(numbers);
// Growable list created with List.generate
var squares = List<int>.generate(5, (i) => i * i);
print(squares);
// Attempting to grow a fixed-length list throws
try {
numbers.add(4);
} on UnsupportedError catch (e) {
print('Cannot add to fixed-length list: ${e.runtimeType}');
}
}
The first list is a growable literal. The second is a fixed-length list filled
with zeros, and its elements are then assigned by index. List.generate
builds the list of squares from a function of the index. Calling
add on the fixed-length list throws an UnsupportedError,
which the try block catches.
$ dart main.dart [apple, banana, orange] [1, 2, 3] [0, 1, 4, 9, 16] Cannot add to fixed-length list: UnsupportedError
Accessing List Elements
Elements are read with index notation. The first and
last getters and the length property give the
boundary elements and the size. Out-of-range indices are an error, so the
index must be checked when it comes from outside the program.
void main() {
var colors = ['red', 'green', 'blue', 'yellow'];
// Access by index
print(colors[0]);
print(colors[3]);
// elementAt is equivalent to [] for valid indices
print(colors.elementAt(2));
// First and last elements
print('First: ${colors.first}');
print('Last: ${colors.last}');
// Length
print('Length: ${colors.length}');
// Out-of-range access with [] throws RangeError
try {
print(colors[10]);
} on RangeError catch (e) {
print('RangeError: ${e.message}');
}
// Safe lookup: check bounds before accessing
var index = 10;
if (index < colors.length) {
print(colors[index]);
} else {
print('Index $index is out of range');
}
}
colors[0], colors.elementAt(2) and colors[3]
return elements at valid indices. elementAt does not add any
safety: it reports an out-of-range index with the same RangeError
as []. To avoid the error, compare the index with
length before reading the element.
$ dart main.dart red yellow blue First: red Last: yellow Length: 4 RangeError: Invalid value Index 10 is out of range
Adding and Removing Elements
Growable lists support adding and removing elements in place. The methods below modify the list they are called on.
void main() {
var numbers = [1, 2, 3];
// Adding elements
numbers.add(4);
numbers.addAll([5, 6]);
numbers.insert(0, 0);
print('After adding: $numbers');
// Removing elements
numbers.remove(6);
numbers.removeAt(0);
numbers.removeLast();
print('After removing: $numbers');
// Removing by condition
numbers.removeWhere((n) => n % 2 == 0);
print('After removeWhere (even): $numbers');
// Clear all
numbers.clear();
print('After clear: $numbers');
}
add appends one element, and addAll appends every
element of an iterable. insert places an element at a given index.
remove deletes the first occurrence of a value, while
removeAt and removeLast delete by position.
removeWhere deletes every element that matches a condition.
$ dart main.dart After adding: [0, 1, 2, 3, 4, 5, 6] After removing: [1, 2, 3, 4] After removeWhere (even): [1, 3] After clear: []
List Iteration
Dart offers several ways to visit each element. A for loop with
an index gives access to the position, a for-in loop is the
simplest form, and forEach takes a function for each element.
void main() {
var languages = ['Dart', 'Python', 'Java', 'C#'];
// for loop with index
print('for loop:');
for (var i = 0; i < languages.length; i++) {
print('$i: ${languages[i]}');
}
// for-in loop
print('\nfor-in loop:');
for (var lang in languages) {
print(lang);
}
// forEach
print('\nforEach:');
languages.forEach((lang) => print(lang));
// map returns a lazy Iterable; toList materializes it
print('\nmap:');
var upper = languages.map((lang) => lang.toUpperCase()).toList();
print(upper);
}
The for loop prints the index together with each value. The
for-in loop and forEach print the values only.
map returns a lazy Iterable, so toList
is called to build a new List that holds the transformed values.
$ dart main.dart for loop: 0: Dart 1: Python 2: Java 3: C# for-in loop: Dart Python Java C# forEach: Dart Python Java C# map: [DART, PYTHON, JAVA, C#]
List Manipulation
Lists provide methods for sorting, reversing, filtering and slicing. Some of these modify the list, while others return a new value or a lazy view.
import 'dart:math';
void main() {
var numbers = [5, 2, 8, 1, 7, 3, 9, 4, 6];
// Sorting in place
numbers.sort();
print('Sorted: $numbers');
// reversed is a lazy view; toList creates a new list
var reversed = numbers.reversed.toList();
print('Reversed: $reversed');
// Filtering with where (lazy) and toList
var even = numbers.where((n) => n % 2 == 0).toList();
print('Even numbers: $even');
// sublist: start inclusive, end exclusive
var middle = numbers.sublist(2, 5);
print('Middle sublist: $middle');
// shuffle with a seeded Random is deterministic
numbers.shuffle(Random(42));
print('Shuffled: $numbers');
}
sort and shuffle change the list in place.
reversed and where return lazy iterables, so
toList is needed to store the results in a list. The end index of
sublist is exclusive, so sublist(2, 5) returns three
elements. The shuffle uses a fixed seed, so the output is the same on every run.
Without a seed, the order changes between runs.
$ dart main.dart Sorted: [1, 2, 3, 4, 5, 6, 7, 8, 9] Reversed: [9, 8, 7, 6, 5, 4, 3, 2, 1] Even numbers: [2, 4, 6, 8] Middle sublist: [3, 4, 5] Shuffled: [4, 5, 9, 1, 3, 2, 6, 7, 8]
Spread Operator and Collection If/For
List literals accept the spread operator ..., and they can contain
if and for elements. These make it possible to build
a list from other lists in one expression.
void main() {
var list1 = [1, 2, 3];
var list2 = [4, 5, 6];
// Spread operator
var combined = [...list1, ...list2];
print('Combined: $combined');
// Collection if
var includeZero = true;
var numbers = [if (includeZero) 0, ...list1];
print('With optional zero: $numbers');
// Collection for
var squares = [for (var n in list2) n * n];
print('Squares: $squares');
}
The spread operator copies every element of a list into the literal.
The if element adds 0 only when the condition is true.
The for element computes one value per element of
list2.
$ dart main.dart Combined: [1, 2, 3, 4, 5, 6] With optional zero: [0, 1, 2, 3] Squares: [16, 25, 36]
Fixed-length and Unmodifiable Lists
List.filled creates a fixed-length list, and
List.empty(growable: true) creates an empty growable list.
List.unmodifiable creates a list that cannot be changed at all.
Mutating an unmodifiable list throws an UnsupportedError.
void main() {
var fixed = List<int>.filled(3, 0);
var growable = List<int>.empty(growable: true);
growable.add(1);
print('fixed: $fixed');
print('growable: $growable');
// List.unmodifiable: any mutation throws
var readOnly = List.unmodifiable([1, 2, 3]);
try {
readOnly.add(4);
} on UnsupportedError catch (e) {
print('Unmodifiable list: ${e.runtimeType}');
}
// A const list literal is also unmodifiable
const constList = [10, 20];
print('const: $constList');
}
The fixed-length list keeps its size of three, and the growable list accepts
add. The unmodifiable list rejects add with an
UnsupportedError. A const list literal is
unmodifiable as well, and it is created at compile time.
$ dart main.dart fixed: [0, 0, 0] growable: [1] Unmodifiable list: UnsupportedError const: [10, 20]
Best Practices
- Type annotations: Give list variables an explicit element type, such as
List<String>, when the literal could be ambiguous. - Fixed or growable: Use a growable list when the size changes; use
List.filledwhen the size is known and fixed. - Read-only data: Return
List.unmodifiablefrom APIs that must not let callers change the contents. - Bounds checks: Check the index against
lengthbefore indexing data that comes from outside the program. - Lazy results: Remember that
map,whereandreversedreturn lazy iterables; calltoListwhen a list is needed.
Source
This tutorial covered Dart's List with practical examples demonstrating its key features and usage patterns.
Author
List all Dart tutorials.