Skip to content

Generic Functions

A generic function declares one or more type parameters between angle brackets after the function name. Those type parameters are then used in the parameter list and return type, letting the types flow through from call site to return value.

function identity<T>(value: T): T {
return value;
}

T is a type parameter — a name for a type the caller will provide. Call it with a concrete type:

const name = identity<string>('AceIt'); // name: string
const count = identity<number>(10); // count: number

Or let TypeScript infer T from the argument:

const name = identity('AceIt'); // T inferred as string
const count = identity(10); // T inferred as number

Inference works whenever the argument’s type is unambiguous. You supply the type parameter explicitly only when inference cannot resolve it or when you want to be explicit for documentation.

Type parameters work naturally with arrays and array methods:

function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const q = first(questions); // T inferred as TriviaQuestion; result: TriviaQuestion | undefined
const n = first([1, 2, 3]); // T inferred as number; result: number | undefined

first works correctly for any array type. The return type is always consistent with the input — T | undefined because the array might be empty.

AceIt’s shuffle helper in Module 04 was typed as (arr: string[]): string[] — it only worked with string arrays. With a type parameter, it works for any array:

function shuffle<T>(arr: T[]): T[] {
return [...arr].sort(() => Math.random() - 0.5);
}
const answers = shuffle(['Mercury', 'Venus', 'Mars', 'Earth']); // string[]
const indices = shuffle([0, 1, 2, 3]); // number[]
const qs = shuffle(questions); // TriviaQuestion[]

T is inferred from the input array in every call. The return type always matches — TypeScript knows shuffle returns the same type it receives.

This is the function the entire module has been building toward:

export async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
return response.json() as Promise<T>;
}

The caller supplies T at the call site:

const data = await fetchJson<ApiResponse>(url);
// data: ApiResponse — the type flows through, no cast needed at the call site

TypeScript substitutes ApiResponse for T and knows that data is ApiResponse. The as Promise<T> inside the function body is the one cast needed — response.json() returns Promise<any>, and we assert the type at the boundary where we actually know what to expect.

This is the correct pattern: one cast at the boundary, zero casts downstream. Every consumer of fetchJson<T> gets full type safety without any casts of their own.

With the generic version, fetchQuestions becomes cleaner:

export async function fetchQuestions(
amount: number = 10,
difficulty?: Difficulty,
): Promise<TriviaQuestion[]> {
let url = `${BASE_URL}?amount=${amount}&type=multiple`;
if (difficulty) url += `&difficulty=${difficulty}`;
const data = await fetchJson<ApiResponse>(url); // data: ApiResponse — fully typed
if (data.response_code === ResponseCode.NoResults) {
throw new Error('Not enough questions available. Try a different setting.');
}
if (data.response_code !== ResponseCode.Success) {
throw new Error(`API error (code ${data.response_code})`);
}
return data.results.map(normalizeQuestion); // data.results: RawQuestion[] — typed
}

data.results is RawQuestion[]. data.response_code is number. TypeScript knows both because ApiResponse declares them — and ApiResponse arrived through the type parameter, not through a cast.

Functions can have more than one type parameter:

function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const entry = pair('Mercury', 1); // [string, number]

Multiple type parameters are separated by commas. Each is independently inferred or supplied. AceIt does not need multiple parameters in its generic functions, but the pattern is common in utility libraries.

In api.ts:

  1. Replace the non-generic fetchJson from Module 04 with the generic version: async function fetchJson<T>(url: string): Promise<T>.
  2. Update shuffle to be generic: function shuffle<T>(arr: T[]): T[].
  3. Update fetchQuestions to call fetchJson<ApiResponse>(url) and remove any casts on data.
  4. Run npx tsc --noEmit and confirm the file compiles cleanly with no any or unsafe casts at call sites.
  • A type parameter is declared with angle brackets after the function name: function fn<T>.
  • TypeScript infers type parameters from call-site arguments — explicit supply is only needed when inference fails.
  • shuffle<T>(arr: T[]): T[] generalizes a string-only function to work with any array type.
  • fetchJson<T> carries the expected type through without requiring a cast at every call site.
  • The one necessary as Promise<T> cast lives inside the function, at the boundary with the untyped fetch API — all consumers get a typed result.