Skip to content

Interfaces

A type alias can describe an object shape. So can an interface. The syntax is different, the semantics are mostly the same, and the choice between them matters in specific situations (covered in Lesson 05). This lesson focuses on interfaces — what they are, how to write them, and how they model AceIt’s data.

An interface declares a named object shape:

interface TriviaQuestion {
question: string;
category: string;
difficulty: string;
correctAnswer: string;
allAnswers: string[];
}

Every property is listed with its name and type. This is the contract for a trivia question throughout the app — any object that satisfies this shape is a valid TriviaQuestion.

Once defined, an interface works exactly like any other type:

function renderQuestion(question: TriviaQuestion, index: number, total: number): void {
console.log(`${index + 1} / ${total}: ${question.question}`);
}
const sample: TriviaQuestion = {
question: 'What planet is closest to the Sun?',
category: 'Science & Nature',
difficulty: 'easy',
correctAnswer: 'Mercury',
allAnswers: ['Mercury', 'Venus', 'Mars', 'Earth'],
};
renderQuestion(sample, 0, 10); // ✓

TypeScript checks that sample has all the required properties and that their types match. Passing an object that is missing correctAnswer, or has allAnswers as a number[], is a compile error.

One of the most valuable uses of interfaces is describing external data. AceIt fetches questions from the Open Trivia DB API. The raw response has a specific shape that differs from what the app uses internally:

interface RawQuestion {
type: string;
difficulty: string;
category: string;
question: string;
correct_answer: string;
incorrect_answers: string[];
}
interface ApiResponse {
response_code: number;
results: RawQuestion[];
}

RawQuestion reflects the API’s snake_case property names. TriviaQuestion is the normalized shape the app works with — camelCase, HTML decoded, with answers pre-shuffled. The boundary between them is a normalizeQuestion function that converts one to the other.

This separation matters: if the API changes a field name, you update RawQuestion and the compiler shows you everywhere the change has downstream effects.

interface HighScore {
score: number;
total: number;
date: string;
}

This object is serialized to localStorage and deserialized back. Because the shape is declared as an interface, TypeScript checks that JSON.parse(raw) as HighScore produces the right shape — and that any code writing to localStorage provides all three fields.

Interfaces describe contracts, not classes

Section titled “Interfaces describe contracts, not classes”

Interfaces are often described as defining contracts — they say what shape an object must have, not how it is constructed. An object literal, a class instance, or the return value of a factory function can all satisfy the same interface as long as the shape matches:

interface HighScore {
score: number;
total: number;
date: string;
}
// Object literal — satisfies HighScore
const entry: HighScore = { score: 8, total: 10, date: 'Jan 15, 2024' };
// Class instance — also satisfies HighScore if it has the right properties
class QuizResult {
constructor(
public score: number,
public total: number,
public date: string,
) {}
}
const result: HighScore = new QuizResult(8, 10, 'Jan 15, 2024'); // ✓

Structural typing applies to interfaces the same way it applies to inline object types — the shape is what matters, not the origin.

In types.ts, write full interfaces for AceIt’s three core data shapes:

  1. interface RawQuestion — matching the Open Trivia DB API response: type, difficulty, category, question, correct_answer: string, incorrect_answers: string[].
  2. interface TriviaQuestion — the normalized app shape: question, category, difficulty (use your Difficulty type alias), correctAnswer, allAnswers: string[].
  3. interface ApiResponse — wrapping the API envelope: response_code: number, results: RawQuestion[].
  4. interface HighScorescore: number, total: number, date: string.

Export all four. Run npx tsc --noEmit and confirm no errors.

  • interface declares a named object shape — every property is listed with its name and type.
  • Interfaces work as types anywhere — function parameters, variable annotations, return types.
  • Use interfaces to describe API response shapes — the compiler catches downstream effects when the shape changes.
  • Structural typing applies to interfaces — any object with at least the required properties satisfies the interface.
  • Interfaces describe contracts between parts of the code, regardless of how the object is constructed.