skip to content

Collections

Dart's List, Set, Map and Iterable, with lazy iteration, collection if/for and spread literals, and unmodifiable views. Interviewers check lazy versus eager evaluation and idiomatic building.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

18

In Dart, what is the difference between a growable and a fixed-length List, and which does List.filled create by default?

level: juniorimportance: must knowfreq 52%

answer

  1. same static type, different runtime behaviour
  2. literals and generate grow
  3. filled and empty default growable: false
  4. length changes throw UnsupportedError
  5. one shared fill object

basics

~20 s

A growable Dart List can add and remove elements; a fixed-length one only lets you overwrite them, and length changes throw UnsupportedError. List.filled and List.empty default to fixed-length, while literals, List.of and List.generate are growable.

solid answer

~40 s

Both are plain `List<E>`; the difference is runtime behaviour. A **growable** list — from a literal, `List.of`, `List.from`, `List.generate` or `toList()` — supports `add`, `remove`, `insert` and `clear`. A **fixed-length** list — the default for `List.filled` and `List.empty` — lets you overwrite elements, `sort` or `setRange`, but anything that changes the length throws an `UnsupportedError` at runtime. Each constructor has a `growable:` argument to override the default. Two traps: `List.filled(3, [])` stores the **same** inner list in every slot, so use `List.generate` for fresh objects; and with null safety the unnamed `List()` constructor is gone and you cannot grow a non-nullable list by setting `length`.

code

dart · 17 lines
dart
void main() {
  final fixed = List<int>.filled(3, 0);
  fixed[1] = 42; // fine: overwrite
  try {
    fixed.add(7);
  } on UnsupportedError catch (e) {
    print(e); // Unsupported operation: Cannot add to a fixed-length list
  }

  final growable = List<int>.filled(3, 0, growable: true);
  growable.add(7);
  print(growable); // [0, 0, 0, 7]

  final rows = List.generate(2, (_) => <int>[]);
  rows[0].add(1);
  print(rows); // [[1], []]
}

go deeper

for a junior

Know which constructors give growable lists and which give fixed-length ones, and that List.filled shares one fill object across all slots.

for a middle

Explain which operations a fixed-length list allows, why the failure is a runtime UnsupportedError, and what null safety removed.

for a senior

Use fixed-length lists deliberately where size is an invariant, and catch shared-fill and List.empty default bugs in review.

for a principal

Set conventions for how lists are created in shared code so that fixed versus growable intent is visible to readers and reviewers.

## Two kinds of List Every Dart `List` is either **growable** or **fixed-length**, and which one you get depends on how it was created, not on its type: both are plain `List<E>`, and nothing in the static type tells them apart. - A **growable list** supports the whole `List` API. It keeps an internal buffer that grows when needed, so a sequence of `add` calls runs in amortized constant time. - A **fixed-length list** has a length set at creation. You can read and overwrite its elements, but any operation that would change the length throws an `UnsupportedError` at runtime (for example *Cannot add to a fixed-length list*). ## Which constructor gives which | How the list is created | Default | |---|---| | a literal: `[]`, `<int>[1, 2]` | growable | | `List.of(iterable)` / `List.from(iterable)` | growable (`growable: true`) | | `List.generate(n, (i) => ...)` | growable (`growable: true`) | | `iterable.toList()` | growable (`growable: true`) | | `List.filled(n, value)` | **fixed-length** (`growable: false`) | | `List.empty()` | **fixed-length**, length 0 (`growable: false`) | Every constructor in the table except the literal accepts a `growable:` named argument to flip the default. `List.empty()` with its default is a fixed-length list of length zero, which is almost never what someone reaching for "an empty list" wants; `<E>[]` or `List.empty(growable: true)` is. ## What a fixed-length list allows Allowed, because the length does not change: - index assignment `list[i] = v`, `setAll`, `setRange`, `fillRange`; - `sort` and `shuffle`, which reorder in place. Throws `UnsupportedError`, because the length would change: - `add`, `addAll`, `insert`, `insertAll`; - `remove`, `removeAt`, `removeLast`, `removeWhere`, `clear`; - assigning to `length`. Choose a fixed-length list when the size is part of the meaning — a buffer of 26 letter counters, a grid of cells — so that an accidental `add` fails loudly instead of silently corrupting the shape. ## The List.filled sharing trap `List.filled(n, fill)` stores **the same `fill` object** at every position. For immutable values such as `0` or `''` that is harmless. For a mutable object it is a classic bug: ```dart final shared = List.filled(3, <String>[]); shared[0].add('dart'); print(shared); // [[dart], [dart], [dart]] final separate = List.generate(3, (_) => <String>[]); separate[0].add('dart'); print(separate); // [[dart], [], []] ``` `List.generate` calls its generator **once per index**, in increasing index order, so each slot gets its own object. It also gives you the index, which makes it the natural way to build computed lists such as `List.generate(5, (i) => i * i)`. ## Null safety changed two things 1. The **unnamed `List()` constructor is gone**. It created a list of a given length with uninitialized slots, which would break soundness for a non-nullable element type, so calling `List()` is an error in null-safe code. Use a literal, `List.filled`, `List.generate` or `List.empty` instead. 2. **Growing a list by setting `length`** is only possible when the element type is nullable, because new slots are filled with `null`. `<int>[1, 2, 3]..length = 5` throws at runtime; truncating (`length = 1`) is always fine on a growable list. ## Putting it together ```dart final letterCounts = List<int>.filled(26, 0); // fixed, 26 zeros letterCounts['c'.codeUnitAt(0) - 97]++; // allowed: overwrite final words = <String>[]; // growable words.add('hello'); // allowed // letterCounts.add(1); // UnsupportedError at runtime ``` ## Interview summary - **Defaults:** literals, `List.of`, `List.from`, `List.generate` and `toList()` grow; `List.filled` and `List.empty` do not, unless `growable: true` is passed. - **Failure mode:** changing the length of a fixed-length list is a **runtime** `UnsupportedError`, not a compile error, because both kinds share the `List` type. - **Allowed on fixed-length:** overwriting, `sort`, `shuffle`, `setRange`, `fillRange`. - **Shared fill:** `List.filled` puts one object in every slot; `List.generate` creates one per index. - **Null safety:** no `List()` constructor, and no growing a non-nullable list through `length`. A good answer also says *why* you would pick one: fixed-length when the size is an invariant of the data, growable everywhere else — which in everyday Flutter and Dart code means the literal almost every time.

  • Why does List.filled(3, <String>[]) behave surprisingly in Dart when you add to one element?
    `List.filled` stores the same `fill` object at every index, so all three slots point to one inner list and an `add` through any index shows up in all of them. `List.generate(3, (_) => <String>[])` calls the generator per index and gives each slot its own list.
  • What replaced the unnamed List() constructor in null-safe Dart?
    Nothing directly: `List()` made uninitialized slots, which is unsound for non-nullable element types, so it was removed. Use a literal for an empty growable list, `List.filled` or `List.generate` for a known length, and `List.empty(growable: true)` if you need a constructor call.
  • Can you sort a fixed-length Dart List?
    Yes. `sort`, `shuffle`, index assignment, `setAll`, `setRange` and `fillRange` keep the length, so they work on a fixed-length list. Only operations that add or remove elements, or assign `length`, throw `UnsupportedError`.

saying these in an interview costs you the question

  • List.filled returns a growable list by default.
  • Adding to a fixed-length list is a compile-time error.
  • A fixed-length list cannot have its elements changed.
  • List.filled(3, []) creates three separate inner lists.
  • List() is the way to make an empty list in modern Dart.
open as a page

In Dart, what is the difference between an Iterable and a List, and why does list.map(...) not give you a List?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A Dart Iterable is any sequence you can step through; a List adds indexing, a stored length and mutation. map, where and similar methods return a lazy Iterable that computes nothing until iterated, so you call toList() to get a List.

open as a page

In Dart, how do collection if elements and the spread operator let a Flutter build method assemble a children list without a temporary list?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A collection if element adds its element only when the condition is true, and a spread ... inserts every element of another iterable in place, so a whole children list, conditional groups included, is one declarative literal instead of a mutable list.

open as a page

In Dart, why can code still call add() on a list held in a final variable, and what actually makes a list unmodifiable?

level: juniorimportance: must knowfreq 62%

basics

~20 s

final only stops the variable from being reassigned; the list object it points to stays growable. A list rejects writes only when the object itself is unmodifiable: a const literal, a List.unmodifiable copy or an UnmodifiableListView, and then writes throw UnsupportedError at runtime.

open as a page

When counting word frequencies in a Dart Map<String, int>, how do update, putIfAbsent and the [] operator differ?

level: middleimportance: must knowfreq 50%

basics

~20 s

In a Dart map, [] returns a nullable value, so counting needs counts[w] = (counts[w] ?? 0) + 1. update(w, (n) => n + 1, ifAbsent: () => 1) does it in one call; putIfAbsent returns the stored value and suits get-or-create grouping.

open as a page

In Dart, when a Cart class exposes its items read-only, how does returning List.unmodifiable(_items) differ from returning UnmodifiableListView(_items)?

level: middleimportance: must knowfreq 50%

basics

~20 s

List.unmodifiable copies the items into a new frozen list, so each call costs O(n) and callers hold a snapshot; UnmodifiableListView wraps the private list in O(1), so callers see later additions. Both block list writes, and neither freezes the items themselves.

open as a page

In Dart, what iteration order does a Map or Set literal guarantee, and when would you choose HashMap or SplayTreeMap instead?

level: middleimportance: should knowfreq 40%

basics

~10 s

Dart map and set literals create a LinkedHashMap or LinkedHashSet, which iterate in key insertion order. Pick HashMap when order truly does not matter, and SplayTreeMap when keys must stay sorted by a comparator.

open as a page

In Dart, which Iterable operations throw on an empty sequence, and what are the safe alternatives such as fold and firstOrNull?

level: middleimportance: should knowfreq 40%

basics

~10 s

first, last, single, reduce and firstWhere without orElse throw a StateError on an empty Dart Iterable. fold returns its initial value, and firstOrNull, lastOrNull, singleOrNull and elementAtOrNull return null instead.

open as a page

In Dart, how does a collection for element compare with map().toList() for building lists and maps, including filtering with a nested if?

level: middleimportance: should knowfreq 45%

basics

~20 s

A collection for element loops inside the literal and inserts one element per iteration; a nested if filters, fors nest, and fixed elements can surround it, replacing where().map().toList() chains and Map.fromIterable with one eager literal.

open as a page

In Dart collection literals, how do the spread ..., the null-aware spread ...? and the Dart 3.8 null-aware element ?x each handle null?

level: middleimportance: should knowfreq 48%

basics

~20 s

Plain ...xs needs a non-nullable iterable; ...?xs inserts nothing when xs itself is null but still copies null elements inside it; the Dart 3.8 null-aware element ?x inserts one value only when it is not null.

open as a page

In Dart, why does final adminTiles = [ListTile(title: Text('Users'))] reject a later adminTiles.add(const Divider()), and when should a literal carry <Widget>?

level: middleimportance: should knowfreq 38%

basics

~20 s

Without a context type, a literal infers its element type from its own elements, so that list is List<ListTile> and a Divider cannot be added; write <Widget>[...] or declare List<Widget> when the list must hold other widget types or starts empty.

open as a page

In Dart, why should a class copy a List it receives in its constructor, and how do List.of, List.unmodifiable and List.unmodifiableOf differ?

level: middleimportance: should knowfreq 36%

basics

~20 s

Storing the caller's list means the caller can still change it later, silently changing the object; copying in the constructor breaks that link. List.of makes a typed growable copy, List.unmodifiable a frozen one, and Dart 3.13's List.unmodifiableOf a frozen one with a statically typed argument.

open as a page

Why can a Dart Map or Set fail to find a key that was inserted earlier, and how do == and hashCode cause it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Dart hash maps and sets locate keys by hashCode, then confirm with ==. A key without value equality (a List or plain class), a key mutated after insertion, or inconsistent == and hashCode makes an entry exist yet be unfindable.

open as a page

A Dart log viewer stores logLines.where(isError).map(parseEntry) in a variable and parseEntry runs twice per error line; why, and how do you fix it?

level: seniorimportance: should knowfreq 42%

basics

~10 s

A Dart where/map chain is a lazy Iterable that recomputes on every iteration, so two consumers mean two passes and two parses per error line. Materialise once with toList() and reuse the list.

open as a page

In a Flutter Column with spacing, why does showBadge ? badge : const SizedBox.shrink() lay out differently from if (showBadge) badge, and which should you use?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A ternary always inserts a child, so a zero-size SizedBox.shrink still counts: Column's spacing adds one extra gap and spaceBetween or spaceAround shares free space with it; an if element removes the child, so prefer it unless the slot must be kept.

open as a page

In Dart, a Cart constructor declares {this.items = const []}; why does cart.items.add(item) work for some carts and throw UnsupportedError for others?

level: seniorimportance: should knowfreq 28%

basics

~20 s

A parameter default must be a compile-time constant, so carts built without items get the unmodifiable, canonicalized const [] and add throws; carts given a list share the caller's growable list and add succeeds. Copy the argument into a fresh list instead.

open as a page

When a Flutter state class holds nested collections, how do listEquals, ListEquality and DeepCollectionEquality differ in what they compare?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Flutter's listEquals and package:collection's ListEquality compare two lists element by element using each element's ==, so nested lists inside are compared by identity; DeepCollectionEquality recurses into nested lists, sets, maps and iterables and offers a matching hash.

open as a page

In Dart, how does a sync* generator with yield and yield* work, and when does its body actually run?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

A Dart sync* function returns an Iterable at once without running its body; the body runs when iterated, pausing at each yield until the next value is requested. yield* inserts every element of another iterable, and each new iteration reruns the body.

open as a page