# Lists

Course: [Dart](https://www.devtutorials.ai/learn/dart) · Section: Collections
Source: https://www.devtutorials.ai/learn/dart/collections/lists

> How to create lists, read and change items by index, add, remove, search and sort items, and use spread, collection if and collection for.

## What Is a List?

**List** = one variable that holds many values, **in order**.

For example:

- the names of all students in a class
- the marks of one student in five subjects
- the days of the week

Each value in a list is called an **element** (or item).

**Simple lesson:** a list keeps many values together, in a fixed order, under one name.

### A Real-World Example

Look at your class attendance register.

- It is **one** register, with **many** names.
- The names are in **order**: first, second, third...
- A new student is **added** at the end.
- A student who leaves is **removed**.

A Dart list is that register. One name holds them all, in order.

Every **Run** box on this page runs on the course's Dart runner in your browser.

Some boxes fail on purpose, to show the errors you get with lists.

## What You Will Learn

In this lesson, you will learn:

1. How to create lists, typed lists, fixed-length and growable lists.
2. How to read and change items with an index.
3. How to add, remove and search for items.
4. Useful list operations, like `sort()`, `sublist()` and `join()`.
5. The spread operator, collection `if` and `for`, and unmodifiable lists.

## List Fundamentals

### Creating Lists with List Literals

**List literal** = values inside `[ ]`, separated by commas.

```dart runnable title="Your first lists"
void main() {
  var students = ['Asha', 'Ravi', 'Priya'];
  var marks = [78, 92, 65];
  print(students);
  print(marks);
  print('There are ${students.length} students.');
}
```

### Typed Lists

**Typed list** = a list that holds only one type, written `List<type>`.

- `List<String>` holds only text.
- `List<int>` holds only whole numbers.
- `var marks = [78, 92];` is a `List<int>` too. Dart infers it.

Adding the wrong type is refused before the program runs:

```dart
List<int> marks = [78, 92];
marks.add('A+');
```

Dart says: `The argument type 'String' can't be assigned to the parameter type 'int'.`

### Empty Lists

```dart runnable title="Starting with an empty list"
void main() {
  var absentees = <String>[];
  print(absentees);
  print(absentees.isEmpty);
  absentees.add('Kiran');
  print(absentees);
}
```

Write the type before `[]`, like `<String>[]`. Otherwise Dart cannot tell what the list will hold.

### Fixed-Length and Growable Lists

- **Growable list** = can get longer or shorter. Every list literal is growable.
- **Fixed-length list** = always has the same number of items. Made with `List.filled(count, value)`.

```dart runnable expect-error title="A fixed-length list cannot grow"
void main() {
  var benches = List.filled(3, 'empty');
  benches[1] = 'Asha';
  print(benches);
  benches.add('Ravi');
}
```

- `benches[1] = 'Asha'` works. You may **change** items.
- `add` fails: `Cannot add to a fixed-length list`.

**Simple lesson:** write a list with `[ ]`, and give it a type so Dart can check every item.

## Accessing and Updating Elements

### Accessing Elements by Index

**Index** = the position number of an item. It **starts at 0**.

- `students[0]` is the **first** item.
- `students[1]` is the **second** item.
- The **last** index is `length - 1`.

```dart runnable title="Reading and changing items by index"
void main() {
  var students = ['Asha', 'Ravi', 'Priya', 'Kiran'];
  print(students[0]);
  print(students[3]);
  students[1] = 'Ravindra';
  print(students);
  print(students.first);
  print(students.last);
}
```

### Updating Elements

`students[1] = 'Ravindra';` replaces the item at index 1. The length stays the same.

### first, last and single

- **`first`** = the first item.
- **`last`** = the last item.
- **`single`** = the only item. It works only when the list has **exactly one** item.

### Index Out of Range

```dart runnable expect-error title="There is no item at index 3"
void main() {
  var marks = [78, 92, 65];
  print(marks[3]);
}
```

The error is a `RangeError`. A list of 3 items has indexes 0, 1 and 2 only.

A list's `[ ]` index and a for loop's counter both usually start at 0.

So a loop over a list runs while `i < list.length`, not `i <= list.length`. The `<=` version goes one step too far and crashes.

**Simple lesson:** indexes start at 0, so the last item is at `length - 1`.

## List Properties

| Property | Gives | Example result |
|---|---|---|
| `length` | how many items | `3` |
| `isEmpty` | `true` if there are no items | `false` |
| `isNotEmpty` | `true` if there is at least one item | `true` |
| `reversed` | the items in reverse order | `(65, 92, 78)` |

```dart runnable title="List properties"
void main() {
  var marks = [78, 92, 65];
  print(marks.length);
  print(marks.isEmpty);
  print(marks.isNotEmpty);
  print(marks.reversed);
  print(marks.reversed.toList());
}
```

`reversed` prints with `( )`, not `[ ]`. It is an **Iterable**, not a list. `toList()` turns it back into a list. You will learn Iterables in **Working with Iterables**.

**Simple lesson:** `length` counts the items, and `isEmpty` checks for none.

## Changing a List

Press the buttons and watch the boxes and their indexes move:

Look at two things:

- **`insert(0, ...)`** pushes every item one place to the right. Their indexes change.
- **`removeAt` and `removeLast`** give back the item they removed.

### Adding Elements

| Method | What it does |
|---|---|
| `add(x)` | adds one item at the end |
| `addAll([...])` | adds many items at the end |
| `insert(i, x)` | puts one item at index `i` |
| `insertAll(i, [...])` | puts many items at index `i` |

```dart runnable title="Adding items"
void main() {
  var queue = ['Asha', 'Ravi'];
  queue.add('Priya');
  queue.addAll(['Kiran', 'Meena']);
  queue.insert(0, 'Teacher');
  queue.insertAll(2, ['Imran', 'Zoya']);
  print(queue);
}
```

### Removing Elements

| Method | What it does |
|---|---|
| `remove(x)` | removes the first `x`, gives `true` or `false` |
| `removeAt(i)` | removes the item at index `i`, and gives it back |
| `removeLast()` | removes the last item, and gives it back |
| `removeRange(a, b)` | removes indexes `a` up to (not including) `b` |
| `clear()` | removes every item |

```dart runnable title="Removing items"
void main() {
  var subjects = ['Maths', 'Science', 'Art', 'Music', 'English', 'Hindi'];
  print(subjects.remove('Art'));
  print(subjects.remove('Dance'));
  print(subjects.removeAt(0));
  print(subjects.removeLast());
  print(subjects);
  subjects.removeRange(0, 2);
  print(subjects);
  subjects.clear();
  print(subjects);
}
```

**Simple lesson:** `add` and `insert` put items in, and `remove`, `removeAt` and `clear` take them out.

## Searching Elements

| Method | Gives |
|---|---|
| `contains(x)` | `true` if `x` is in the list |
| `indexOf(x)` | the first index of `x`, or `-1` if it is not there |
| `lastIndexOf(x)` | the last index of `x`, or `-1` |

```dart runnable title="Searching a list"
void main() {
  var houses = ['Red', 'Blue', 'Green', 'Blue'];
  print(houses.contains('Green'));
  print(houses.contains('Yellow'));
  print(houses.indexOf('Blue'));
  print(houses.lastIndexOf('Blue'));
  print(houses.indexOf('Yellow'));
}
```

`-1` means "not found". It can never be a real index, because indexes start at 0.

**Simple lesson:** `contains` says yes or no, and `indexOf` says where, or `-1`.

## List Operations

| Method | What it does |
|---|---|
| `sort()` | puts the items in order, in place |
| `shuffle()` | puts the items in a random order |
| `sublist(a, b)` | a new list with indexes `a` up to `b` |
| `join(sep)` | one String of all items, with `sep` between them |
| `List.generate(n, f)` | a new list of `n` items, made by a function |
| `fillRange(a, b, x)` | sets indexes `a` up to `b` to `x` |
| `getRange(a, b)` | the items from `a` up to `b`, as an Iterable |

```dart runnable title="Sorting, slicing and joining"
void main() {
  var marks = [78, 92, 65, 88, 70];
  marks.sort();
  print(marks);
  print(marks.sublist(1, 3));
  print(marks.getRange(0, 2));

  var names = ['Ravi', 'Asha', 'Priya'];
  names.sort();
  print(names.join(', '));

  var squares = List.generate(5, (i) => i * i);
  print(squares);

  var seats = List.filled(5, 'free');
  seats.fillRange(1, 3, 'taken');
  print(seats);
}
```

- `sort()` changes the list itself. Numbers go smallest first, and text goes A to Z.
- `sublist(1, 3)` includes index 1 but **not** index 3.
- `shuffle()` gives a different order every time, which is good for quizzes and games.

**Simple lesson:** `sort` orders the list, `sublist` copies part of it, and `join` turns it into text.

## Iterating Lists

You learned these in **Loops**. Here they are with lists:

```dart runnable title="Three ways to visit every item"
void main() {
  var subjects = ['Maths', 'Science', 'English'];

  for (var i = 0; i < subjects.length; i++) {
    print('$i: ${subjects[i]}');
  }

  for (var s in subjects) {
    print('I like $s');
  }

  subjects.forEach((s) => print(s.toUpperCase()));
}
```

- **`for` with an index:** when you need the position number.
- **`for-in`:** the simplest way to visit every item.
- **`forEach()`:** a function runs for each item.

**Simple lesson:** use `for-in` to visit items, and a counted `for` when you also need the index.

## Collection Features

### The Spread Operator (...)

**Spread operator (`...`)** = unpacks all items of one list into another.

### The Null-Aware Spread Operator (...?)

**`...?`** = spreads the list only if it is not null. A null list adds nothing.

```dart runnable title="Spreading lists into a new list"
void main() {
  var morning = ['Maths', 'Science'];
  var afternoon = ['Art', 'Games'];
  var timetable = ['Assembly', ...morning, 'Lunch', ...afternoon];
  print(timetable);

  List<String>? extraClasses;
  var full = [...timetable, ...?extraClasses];
  print(full.length);
}
```

### Collection if and Collection for

**Collection if** = add an item only when a condition is `true`.

**Collection for** = add one item for every item of another list.

```dart runnable title="Building a list with if and for"
void main() {
  var isSportsDay = true;
  var rollNumbers = [1, 2, 3];
  var plan = [
    'Assembly',
    if (isSportsDay) 'Sports Day',
    for (var r in rollNumbers) 'Roll $r',
  ];
  print(plan);
}
```

Set `isSportsDay` to `false` and run again. `'Sports Day'` disappears.

**Simple lesson:** `...` copies a whole list in, and `if` or `for` inside `[ ]` build items as you go.

## Unmodifiable Lists

**Unmodifiable list** = a list that can never change. No adding, removing or replacing.

### const Lists

```dart runnable expect-error title="A const list can never change"
void main() {
  const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  print(weekdays);
  weekdays.add('Sat');
}
```

The error is `Unsupported operation: Cannot add to an unmodifiable list`.

### List.unmodifiable()

**`List.unmodifiable(list)`** = makes an unchangeable copy of a list, while the program runs.

```dart runnable title="An unchangeable copy"
void main() {
  var toppers = ['Asha', 'Ravi'];
  var finalToppers = List.unmodifiable(toppers);
  toppers.add('Priya');
  print(toppers);
  print(finalToppers);
}
```

The copy stays `[Asha, Ravi]`, even after the original changes.

**Easy way to remember**

- `final list = [...]` = the **name** cannot point to another list, but the items can change.
- `const list = [...]` = **nothing** can change, ever.
- `List.unmodifiable(x)` = a locked copy, made while running.

**Simple lesson:** use `const` or `List.unmodifiable()` when a list must never change.

## TL;DR

1. **List** = many values, in order, in one variable: `[78, 92, 65]`.
   - **Typed:** `List<int>`. **Empty:** `<String>[]`.
   - **Fixed-length:** `List.filled(3, 0)`. **Growable:** every `[ ]` literal.
2. **Index** = position, starting at 0. `list[0]`, `first`, `last`.
3. **Change:** `add`, `addAll`, `insert`, `remove`, `removeAt`, `removeLast`, `clear`.
   - **Search:** `contains`, `indexOf` (`-1` if missing).
4. **Operations:** `sort`, `sublist`, `join`, `List.generate`, `fillRange`.
5. **Build:** `...` spread, `...?` null-aware spread, collection `if` and `for`.
   - **Locked:** `const` lists and `List.unmodifiable()`.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `list[list.length]` | The last index is `length` | The last index is `length - 1` | A `RangeError`. |
| Starting to count at 1 | `list[1]` is the first item | `list[0]` is the first item | You skip the first item. |
| `add` on `List.filled(...)` | All lists can grow | `List.filled` makes a fixed-length list | `Cannot add to a fixed-length list`. |
| `add` on a `const` list | `const` only fixes the name | A `const` list can never change | `Cannot add to an unmodifiable list`. |
| `var empty = [];` | Dart knows the item type | Write `<String>[]` | A `List<dynamic>`, with no type checks. |
| Expecting `sublist(1, 3)` to include index 3 | The end index is included | The end index is NOT included | One item fewer than expected. |
| Printing `reversed` | It gives a list | It gives an Iterable, shown with `( )` | Output like `(3, 2, 1)`. Add `toList()`. |

## Check Yourself

## Exercises

## What to Remember

- A list holds many values in order. Give it a type, like `List<int>`.
- Indexes start at 0, and the last index is `length - 1`.
- `add`, `insert` and `remove` change a growable list. `List.filled` makes a fixed one.
- `contains` and `indexOf` search, and `sort`, `sublist` and `join` reshape.
- `...`, collection `if` and collection `for` build lists, and `const` lists never change.

Next: **Working with Iterables**. You will learn to transform and question a whole list in one line, with map, where and fold.
