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. What does "type erasure" mean? Does Dart erase generics? Dart's generics are reified — type arguments survive to runtime, so List<int> vs List<String> is detectable with is. That's different from Java where generics are erased.

  3. 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.

  4. 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.