Generic Interfaces and Type Aliases
Generic type parameters are not limited to functions. Interfaces and type aliases can be parameterized too, producing reusable shapes that adapt to any type that fills the parameter.
Generic type aliases
Section titled “Generic type aliases”You have already written type Nullable<T> = T | null as a pattern — here it is as a named generic alias:
type Nullable<T> = T | null;
type NullableHighScore = Nullable<HighScore>; // HighScore | nulltype NullableQuestion = Nullable<TriviaQuestion>; // TriviaQuestion | nullNullable<T> captures a recurring pattern — “this value or null” — and gives it a name. Every use is consistent: change the definition once and every alias updates.
Generic interfaces
Section titled “Generic interfaces”An interface can declare a type parameter the same way a function does:
interface ApiResult<T> { data: T; timestamp: number; source: string;}
const questionResult: ApiResult<TriviaQuestion[]> = { data: questions, timestamp: Date.now(), source: 'opentdb.com',};
const scoreResult: ApiResult<HighScore> = { data: { score: 8, total: 10, date: 'Jan 15, 2024' }, timestamp: Date.now(), source: 'localStorage',};ApiResult<T> describes any wrapped response — the wrapping structure (timestamp, source) stays the same while the data property changes type.
Promise<T> and Array<T> are generic interfaces
Section titled “Promise<T> and Array<T> are generic interfaces”You have been using generic interfaces throughout the course:
const p: Promise<TriviaQuestion[]> = fetchQuestions(10);// Promise<T> with T = TriviaQuestion[]
const arr: Array<string> = ['Mercury', 'Venus'];// Array<T> with T = string — identical to string[]Promise<T> declares that the resolved value will be of type T. Array<T> declares that every element will be of type T. Both are built into TypeScript’s standard library as generic interfaces — you supply the type argument and the library does the rest.
Generic type alias for a callback shape
Section titled “Generic type alias for a callback shape”Generic type aliases work well for reusable callback patterns:
type Handler<T> = (value: T) => void;
type QuestionHandler = Handler<TriviaQuestion>;// (value: TriviaQuestion) => void
type ScoreHandler = Handler<number>;// (value: number) => voidHandler<T> names the pattern “a function that receives a T and returns nothing”. Specializing it with <TriviaQuestion> or <number> produces readable, consistent callback types.
A generic loading state
Section titled “A generic loading state”AceIt’s QuizState is a discriminated union. You could generalize the active branch to carry any data type:
type LoadingState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'ready'; data: T } | { status: 'error'; message: string };
type QuizLoadingState = LoadingState<TriviaQuestion[]>;type ScoreLoadingState = LoadingState<HighScore>;LoadingState<T> captures a common async data pattern — idle, loading, ready with data, or error — and can be applied to any data type. AceIt’s QuizState is a specialized version of this pattern.
When to generalize
Section titled “When to generalize”Not every type benefits from a parameter. Generalize when:
- The same structure appears with different data types in multiple places.
- The relationship between the type parameter and the shape is meaningful (not just coincidental).
Do not generalize speculatively — three similar lines of code is not automatically a sign to abstract. AceIt’s TriviaQuestion and HighScore are intentionally not merged into a generic Question<T> because they describe genuinely different things.
Exercise
Section titled “Exercise”In types.ts:
- Add
type Nullable<T> = T | null. ReplaceHighScore | nullwherever it appears withNullable<HighScore>. - Write
type Handler<T> = (value: T) => void. Definetype ScoreHandler = Handler<{ score: number; total: number }>. - Write
interface Wrapped<T> { value: T; timestamp: number }. Create aWrapped<HighScore>variable. - Review
QuizState— identify which branches carry data and what the data type is. Note how this maps to theLoadingState<T>pattern above, even though AceIt keeps the concrete version.
- Interfaces and type aliases can declare type parameters just like functions do.
Promise<T>andArray<T>are generic interfaces you have been using throughout the course.- Generic type aliases like
Nullable<T>andHandler<T>name recurring patterns and keep them consistent. - Generalize when the same structure recurs with different data types — not speculatively.
- A generic interface describes a shape that adapts to any type that fills the parameter.