Explain generics in Dart

Medium PriorityAsked in ~55% of Flutter interviews

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.

ConceptSyntaxWhy it exists
Generic classclass Box<T> { T value; }One class, many element types
Generic functionT 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 inferenceBox('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

SituationUse
Collection / container of any typeGeneric class (Box<T>, Cache<K, V>)
Utility that works on any type uniformlyGeneric 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

  1. What's the difference between dynamic, Object?, and a generic T? dynamic disables 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.

  2. When would you use a bounded type parameter (<T extends X>)? When the generic code needs to call methods specific to X. Classic examples: <T extends Comparable<T>> to enable sorting, or <T extends Widget> to constrain a builder.

  3. What is covariance, and where does it bite in Dart? Dart generics are covariantList<Dog> is assignable to List<Animal>. That's safe for reads but unsafe for writes (you could add a Cat to a List<Dog>). Dart inserts runtime checks at the unsafe point — possible source of unexpected TypeErrors.

How helpful was this content?

Please sign in to rate this article.