Lesson 12 of 25
Generic Interfaces and Classes
Apply generics beyond standalone functions — to interfaces, type aliases, and classes — and read the generic types you already use every day, like Array<T> and Promise<T>.
Generics aren't limited to standalone functions — the same idea, "a placeholder type filled in per use," applies to interfaces, type aliases, and classes too. You've actually been using generic types since your first array in this track; this lesson makes that explicit.
Generic interfaces
An interface can declare a type parameter exactly like a function:
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: "hello" };
numberBox.value = "oops";
// Type 'string' is not assignable to type 'number'.Box<number> and Box<string> are two distinct, fully separate types built
from the same template — assigning one where the other is expected is an
error, the same as assigning string where number is expected anywhere
else.
You already know this: Array<T>
Array in TypeScript's own standard library is a generic interface, and
T[] has always been shorthand syntax for Array<T> — the two are
identical:
let scores: number[] = [10, 20, 30];
let sameThing: Array<number> = [10, 20, 30]; // exactly the same typeThis is worth internalising: nothing about generics is a separate, advanced feature you're opting into. Every array you've written in this track has been a generic type, filled in with whatever element type you gave it.
Generic type aliases
The same works for type:
type Result<T> = { success: true; data: T } | { success: false; error: string };
function parseNumber(input: string): Result<number> {
const parsed = Number(input);
return Number.isNaN(parsed)
? { success: false, error: "Not a number" }
: { success: true, data: parsed };
}This combines two ideas from earlier lessons — a discriminated union, and a generic — into a pattern you'll recognise everywhere once you've seen it once: a reusable "this either succeeded with data, or failed with a reason" shape.
Generic classes
Classes take a type parameter the same way, applied after the class name and usable anywhere inside — constructor parameters, methods, properties:
class Box<T> {
constructor(public value: T) {}
map<U>(transform: (value: T) => U): Box<U> {
return new Box(transform(this.value));
}
}
const box = new Box(5); // T inferred as number
const doubled = box.map((n) => n * 2); // Box<number>
const asString = box.map((n) => n.toString()); // Box<string>T is inferred from the constructor argument, exactly like a generic
function infers from its parameters. map introduces its own additional
type parameter, U, for the transformed value's type — a method can add
type parameters beyond whatever the class itself declared.
Promise<T>: a generic you already use
Promise is generic over what it eventually resolves to — this is precisely
why await gives back a correctly typed value instead of something generic
or untyped:
function fetchUser(id: string): Promise<{ name: string }> {
return fetch(`/api/users/${id}`).then((response) => response.json());
}
async function run() {
const user = await fetchUser("1"); // user: { name: string }
console.log(user.name); // fine — TypeScript knows the shape
}Without the <{ name: string }> type parameter, Promise<any> would leave
user untyped after await, silently losing checking on everything
downstream — the same problem as any from the previous lesson, just
arriving through a promise instead of a bare variable.
Try it yourself
What to remember
- Interfaces, type aliases, and classes can all take type parameters, using exactly the same syntax and reasoning as a generic function.
T[],Array<T>,Promise<T>, andMap<K, V>are ordinary generic types from the standard library, not separate built-in features.- Applying a generic type with different type arguments (
Box<string>vsBox<number>) produces different, incompatible types. - A method on a generic class can introduce its own additional type parameters beyond the class's own.
Check yourself
4 questions · pass 3/4 to unlock Utility Types, Part 1: Partial, Required, Readonly, Pick, Omit
1.What does Array<T> actually mean, given that arrays are typically written as string[]?
2.Given
class Box<T> { constructor(public value: T) {} }, what is the type ofnew Box(5).value?3.Why does
Promise<T>need a type parameter at all?4.A generic interface
interface Box<T> { value: T }is used asBox<string>in one place andBox<number>in another. Are these the same type?
4 left to answer