Skip to content

Module Recap

Module 05 gave you the tools to write reusable, fully typed code. The signature feature — generic type parameters — lets a single function or interface work correctly with any type the caller provides, without sacrificing the type safety that makes TypeScript worth using.

What generics are — a type parameter is a placeholder that the caller fills in. The type flows through the function without unsafe casts. Array<T> and Promise<T> are the generic types you have used throughout the course.

Generic functionsfunction fn<T> declares T as a type parameter. TypeScript infers T from call-site arguments when possible. fetchJson<T> and shuffle<T> replaced their non-generic versions with functions that work correctly for any type.

Generic interfaces and type aliasesinterface Shape<T> and type Alias<T> parameterize the shape itself. Nullable<T>, Handler<T>, and LoadingState<T> are reusable patterns built on this.

Generic constraintsT extends SomeType restricts what T can be, allowing the function body to access properties guaranteed by the constraint. K extends keyof T restricts K to known property names.

Utility typesPartial, Required, Readonly, Pick, Omit, and Record transform existing types systematically. They compose — the output of one is a valid input to another.

With generics in place, api.ts is fully typed with no unsafe casts at call sites:

import type { ApiResponse, RawQuestion, TriviaQuestion, Difficulty } from './types.js';
import { ResponseCode } from './types.js';
const BASE_URL = 'https://opentdb.com/api.php';
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>;
}
function decodeHtml(html: string): string {
const el = document.createElement('textarea');
el.innerHTML = html;
return el.value;
}
function shuffle<T>(arr: T[]): T[] {
return [...arr].sort(() => Math.random() - 0.5);
}
function normalizeQuestion(raw: RawQuestion): TriviaQuestion {
return {
question: decodeHtml(raw.question),
category: decodeHtml(raw.category),
difficulty: raw.difficulty as Difficulty,
correctAnswer: decodeHtml(raw.correct_answer),
allAnswers: shuffle([raw.correct_answer, ...raw.incorrect_answers]).map(decodeHtml),
};
}
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);
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);
}

fetchJson<ApiResponse> returns Promise<ApiResponse>data is fully typed. data.results is RawQuestion[]. data.response_code is number. No casts downstream.

TermWhat it means
Type parameterA placeholder type declared with <T> that the caller fills in
Generic functionA function with one or more type parameters
Type inferenceTypeScript inferring T from the call-site argument — no explicit supply needed
Generic constraintT extends SomeType — restricts T to types with at least the required shape
keyof TA union of the property names of T
Indexed access typeT[K] — the type of property K on type T
Utility typeA built-in generic type transformation: Partial, Pick, Omit, Record, etc.

Module 06 — Type Narrowing →

Module 06 covers type narrowing — the mechanism TypeScript uses to move from a broad type to a specific one inside a conditional branch. You will narrow union types with typeof and instanceof, use the in operator for property checks, build discriminated unions, write type predicates, and understand unknown, any, and never. AceIt uses all of these to safely handle the API response and model quiz state.