Explain generics in Dart
3 min read
Dart Fundamentals
Generics let you write code that is reusable across types without losing type safety — instead of falling back to dynamic, you parameterise (List<T>, Future<T>, FutureBuilder<T>) and the compiler tracks the real type for you.
| Concept | Syntax | Why it exists |
|---|---|---|
| Generic class | class Box<T> { T value; } | One class, many element types |
| Generic function | T first<T>(List<T> xs) => xs[0]; | Type-safe utilities |
| Bounded type | <T extends Comparable<T>> | Restrict T to types that support what you need |
| Type inference | Box('hi') → Box<String> | Compiler picks T from arguments |
Mental model: T is a slot the compiler fills in at the call site, then enforces consistently everywhere.
Code in action
// Without generics — type info is lost
final box = <dynamic>['Hello'];
String s = box[0]; // hopes & prayers, no compile check
// With generics — type info flows through
class Box<T> {
final T value;
const Box(this.value);
}
final b = Box<String>('Hello');
final n = b.value.length; // ✅ compiler knows it's a String
// Inference does most of the work
final inferred = Box('Hello'); // Box<String>
// Generic function
T first<T>(List<T> xs) => xs.first;
first<int>([1, 2, 3]); // int — explicit
first(['a', 'b']); // String — inferred
// Multiple type parameters
Map<K, V> zip<K, V>(List<K> ks, List<V> vs) =>
Map.fromIterables(ks, vs);
final ages = zip(['Alice', 'Bob'], [30, 25]); // Map<String, int>
// Bounded generic — T must be Comparable
class SortedList<T extends Comparable<T>> {
final _items = <T>[];
void add(T item) { _items.add(item); _items.sort(); }
}
SortedList<int>(); // ✅
// SortedList<Object>(); // ❌ Object isn't Comparable
Where you've already seen generics in Flutter
FutureBuilder<List<Post>>( // Future yields List<Post>
future: fetchPosts(),
builder: (ctx, snap) {
final posts = snap.data; // type: List<Post>?
return ...;
},
);
StreamBuilder<User>(...);
ValueListenableBuilder<int>(...);
Navigator.push<bool>(context, ...); // returns Future<bool?>
These wouldn't be safe without generics — they'd all hand you dynamic.
When to reach for generics
| Situation | Use |
|---|---|
| Collection / container of any type | Generic class (Box<T>, Cache<K, V>) |
| Utility that works on any type uniformly | Generic function (first<T>, clamp<T>) |
You need T to support certain methods (compareTo, +, etc.) | Bounded <T extends Foo> |
The function would otherwise return dynamic or Object? | Add a <T> |
Common mistakes to avoid
// ❌ Reaching for dynamic instead of a generic
dynamic firstItem(List items) => items[0]; // throws away type info
// ✅ Generic — type flows through
T firstItem<T>(List<T> items) => items[0];
// ❌ Forgetting the type argument — get `Object?`
final list = []; // List<dynamic>! 😱
final cache = <String, dynamic>{}; // most uses should be typed
// ✅ Be explicit when literals are ambiguous
final list = <int>[];
final cache = <String, User>{};
// ❌ Putting type arguments on a method call that doesn't need them
items.first<int>; // first isn't generic
// ❌ Variance trap — List<Dog> is NOT a List<Animal> for writes
void feed(List<Animal> animals) => animals.add(Cat());
final dogs = <Dog>[];
feed(dogs); // ❌ would put a Cat into List<Dog>!
// Dart prevents the assignment in the first place if it can detect it.
Interview follow-ups
-
What's the difference between
dynamic,Object?, and a genericT?dynamicdisables type checking entirely.Object?is the top type — type-safe but you have to cast to use members.T(generic) keeps the real type known to the compiler at the call site, so callers don't need to cast. -
What does "type erasure" mean? Does Dart erase generics? Dart's generics are reified — type arguments survive to runtime, so
List<int>vsList<String>is detectable withis. That's different from Java where generics are erased. -
When would you use a bounded type parameter (
<T extends X>)? When the generic code needs to call methods specific toX. Classic examples:<T extends Comparable<T>>to enable sorting, or<T extends Widget>to constrain a builder. -
What is covariance, and where does it bite in Dart? Dart generics are covariant —
List<Dog>is assignable toList<Animal>. That's safe for reads but unsafe for writes (you could add aCatto aList<Dog>). Dart inserts runtime checks at the unsafe point — possible source of unexpectedTypeErrors.
How helpful was this content?
Please sign in to rate this article.