If you check the API documentation for the basic array type List, you will find its actual type is List<E>. Angle brackets like <...> denote a generic type (parameterized type) — a type with formal type parameters. By convention, nearly all type variables are named with a single letter, such as E, T, S, K, V
Why Use Generics?
- The core benefit of generics is enforcing type safety
- Properly specified generics produce more optimized runtime code;
- Generics drastically reduce code duplication.
If you want a list to only store strings, declare it as List<String> — this is a string list.
Once defined as a string-only list, inserting non-string values will trigger an error.
// Static type checking error
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Compile error: 42 is not a StringCode language: PHP (php)
D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:5:12: Error: The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42); // Compile error: 42 is not a StringCode language: PHP (php)
Eliminate Code Redundancy
Suppose you need an abstract interface for an object cache:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
Code language: JavaScript (javascript)
Later, if you require a cache that only stores strings, you must create a separate interface:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Code language: Dart (dart)
You will then need dedicated caches for numbers… forcing repetitive writing of nearly identical code.
For example, you would end up writing something like this:
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}Code language: Dart (dart)
Generics solve this problem in one go by letting you define a single universal interface with type parameters:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}Code language: Dart (dart)
Here, T acts as a type placeholder representing any concrete type passed in by developers later.
Practical Example
// Generic abstract cache base class
abstract class Cache<T> {
T? getByKey(String key);
void setByKey(String key, T value);
void remove(String key);
}
// In-memory cache implementation (backed by Map)
class MemoryCache<T> implements Cache<T> {
final Map<String, T> _storage = {};
@override
T? getByKey(String key) {
return _storage[key];
}
@override
void setByKey(String key, T value) {
_storage[key] = value;
}
@override
void remove(String key) {
_storage.remove(key);
}
}
void main() {
// 1. String cache example
final Cache<String> strCache = MemoryCache<String>();
strCache.setByKey("name", "Tom");
print(strCache.getByKey("name")); // Tom
// 2. Integer cache
final Cache<int> intCache = MemoryCache<int>();
intCache.setByKey("age", 26);
print(intCache.getByKey("age")); // 26
// 3. Custom object cache
final Cache<User> userCache = MemoryCache<User>();
userCache.setByKey("u1", User("Jack", 18));
final User? user = userCache.getByKey("u1");
print(user?.name); // Jack
// Deletion test
userCache.remove("u1");
print(userCache.getByKey("u1")); // null
}
// Custom entity class
class User {
final String name;
final int age;
User(this.name, this.age);
}Code language: Dart (dart)

Using Generic Collection Literals
List, Set, and Map literals all support parameterized generics:
- List / Set: Write
<Type>before the opening bracket - Map: Write
<KeyType, ValueType>before the opening curly brace
Examples:
// String list
var names = <String>['Seth', 'Kathy', 'Lars'];
// String set (auto-duplicate removal)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
// Dictionary with string keys and string values
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Crawler instruction file',
'humans.txt': 'Documentation file for human developers',
};
Code language: PHP (php)
Run the Sample Code
void main() {
// 1. List<String> String List: ordered, allows duplicates, index-accessible
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
print("=== List ===");
print(names); // [Seth, Kathy, Lars, Seth] duplicates permitted
print(names[0]); // Get first element by index: Seth
names.add("Mike"); // Append new element
print("After addition: $names");
// 2. Set<String> String Set: unordered, auto deduplication, no indexes
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
print("\n=== Set (Auto Duplicate Removal) ===");
print(uniqueNames); // {Seth, Kathy, Lars} duplicate Seth removed automatically
print(uniqueNames.contains("Kathy")); // Existence check: true
uniqueNames.remove("Lars");
print("After deleting Lars: $uniqueNames");
// 3. Map<String, String> Key-value dictionary: unique keys, retrieve via key
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Crawler instruction file',
'humans.txt': 'Documentation file for human developers',
};
print("\n=== Map Dictionary ===");
print(pages);
print(pages['index.html']); // Retrieve value by key: Homepage
pages["about.html"] = "About Page"; // Add new key-value pair
print("After adding about.html: $pages");
// Common iteration examples
print("\n=== Iterate List ===");
for (var name in names) {
print("- $name");
}
print("\n=== Iterate All Map Entries ===");
for (var entry in pages.entries) {
print("File: ${entry.key} → Description: ${entry.value}");
}
}Code language: Dart (dart)
Generic Parameters in Constructors
When calling a constructor, specify generic types inside angle brackets after the class name:
var nameSet = Set<String>.of(names);Code language: JavaScript (javascript)
The code below creates a dictionary with int keys and View values:
var views = SplayTreeMap<int, View>();
Code language: HTML, XML (xml)
import 'dart:collection';
void main() {
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
var nameSet = Set<String>.of(names);
print("Original List: $names");
print("Deduplicated Set: $nameSet");
// SplayTreeMap requires dart:collection import
var views = SplayTreeMap<int, String>();
views[5] = "Settings Page";
views[2] = "Homepage";
views[9] = "My Page";
views[1] = "Splash Screen";
print("\nOrdered TreeMap:");
for (var e in views.entries) {
print("${e.key} : ${e.value}");
}
}Code language: PHP (php)
Runtime Types of Generic Collections
Type Reification
Dart generics are reified: full generic type information is preserved at runtime.
You can directly check the concrete generic type of a collection during runtime:
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // Outputs true
}Code language: PHP (php)
Additional comparison: Java uses type erasure for generics, stripping generic parameters at runtime. In Java you can only verify an object is a
List, and cannot distinguish betweenList<String>andList<int>.
* Restricting Generic Parameter Types
Bounds. Skip deep study for now if you haven’t learned classes yet; a basic understanding suffices.
When defining generics, you can restrict passed types to subclasses of a certain supertype. This constraint is called a type bound, implemented via the extends keyword.
1 Restrict to Non-Nullable Types
Force generics to accept only non-nullable types (default upper bound is nullable Object?):
class Foo<T extends Object> {
// T cannot be a nullable type
}
Code language: JavaScript (javascript)
2 Restrict to Subclasses of a Specified Superclass
Use extends to specify a base class, letting you directly call all member methods of the base class:
class Foo<T extends SomeBaseClass> {
String toString() => "Instance Foo<$T>";
}
class Extender extends SomeBaseClass {}
Code language: JavaScript (javascript)
Valid type arguments: the base class itself, or any of its subclasses
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Code language: HTML, XML (xml)
Omitted generic parameters default to the bound type:
var foo = Foo();
print(foo); // Output: Instance Foo<SomeBaseClass>
Code language: PHP (php)
Passing an unrelated type triggers a static compile error:
var foo = Foo<Object>(); // Compile error: Object is not a subclass of SomeBaseClass
Code language: JavaScript (javascript)
* Self-Referential Type Bounds (F-Bounded Generics)
When constraining generics, bounds may reference their own type parameter to create self-constraints, known as F-bounds.
Standard example: Comparable interface Comparable<T>
abstract interface class Comparable<T> {
int compareTo(T o);
}
// T must implement Comparable<T>, only comparable with instances of the same type
int compareAndOffset<T extends Comparable<T>>(T t1, T t2) =>
t1.compareTo(t2) + 1;
class A implements Comparable<A> {
@override
int compareTo(A other) {
// Implement comparison logic
return 0;
}
}
int res = compareAndOffset(A(), A());
Code language: PHP (php)
The constraint T extends Comparable<T> means: T may only compare against instances of the identical type.
* Generic Methods
Regular standalone functions and class methods also support generic type parameters:
T first<T>(List<T> ts) {
// Pre-check logic
T tmp = ts[0];
// Post-processing logic
return tmp;
}
Code language: PHP (php)
Full Example
T first<T>(List<T> ts) {
if (ts.isEmpty) {
throw ArgumentError("List cannot be empty");
}
T tmp = ts[0];
return tmp;
}
void main() {
List<String> strList = ["apple", "banana", "orange"];
String firstStr = first(strList);
print(firstStr); //apple
List<int> numList = [10, 20, 30];
int firstNum = first(numList);
print(firstNum);//10
}Code language: Dart (dart)
The generic parameter <T> applies to three locations:
- Function return type
T - Input parameter type
List<T> - Local variable type
T tmp