Skip to content
Lesson 5 of 15

Step 1 of 7 · Reading · ~4 min

Learn

Strings and Interfaces

Interfaces and Object Types

An interface gives a name to the shape of an object: which fields it has and what type each one holds. By the end of this lesson you can describe an object with optional and read-only fields, build bigger shapes out of smaller ones, and read an optional field without crashing.

Defining an interface

interface Book {
    title: string;
    pages: number;
    subtitle?: string;   // optional: the field may be missing
}

const dune: Book = { title: "Dune", pages: 412 };
const emma: Book = { title: "Emma", pages: 474, subtitle: "A Novel" };
const bad: Book = { title: "Ulysses" };   // compile error: pages is required

A value is accepted when it has at least the listed fields with the listed types. The interface is structural: nothing has to say "I implement Book", an object just has to fit.

Optional fields and undefined

Reading an optional field gives you T | undefined, and you must handle the undefined case before using the value:

console.log(emma.subtitle);          // "A Novel"
console.log(dune.subtitle);          // undefined: the field was left out
console.log(dune.subtitle.length);   // compile error: dune.subtitle is possibly undefined

The compiler refuses the last line because at runtime it would throw. Ruling out undefined before you use the value is called narrowing, and the conditionals lesson shows the if statement that does it. First, a trap that catches people who default an optional field. Checking truthiness instead of undefined also rejects legitimate values such as 0, "" and false:

interface Config {
    retries?: number;     // missing means "use 3"
}

const explicit: Config = { retries: 0 };            // the caller really wants 0
console.log(explicit.retries || 3);                 // 3: BUG, the explicit 0 became 3
console.log(explicit.retries === undefined ? 3 : explicit.retries);   // 0: stays 0

|| falls back on any falsy value (0, "", false, undefined). The second line uses condition ? a : b, which gives a when the condition is true and b otherwise (the conditionals lesson covers it): it falls back to 3 only when the field is really absent.

Readonly and methods

interface Account {
    readonly id: string;        // cannot be reassigned after creation
    balance: number;
    describe(): string;         // a method the object must provide
}

declare const acct: Account;   // `declare`: "a value with this type exists"; it creates nothing
acct.balance = 10;     // fine
acct.id = "x";         // compile error: id is readonly

readonly is a compile-time rule only. After the types are erased nothing stops the assignment, so it protects you from your own mistakes, not from other code.

Index signatures

When the keys are not known in advance but every value has the same type, use an index signature:

interface Stock {
    [item: string]: number;
}
const shelf: Stock = { apples: 4, pears: 9 };
shelf.plums = 2;   // any string key, number value

Extending interfaces

interface Person { name: string; }
interface Teacher extends Person { subject: string; }

const t: Teacher = { name: "Grace", subject: "Maths" };   // needs both fields

extends copies the parent's fields into the child, and interface X extends A, B combines several parents.

Excess property checking

interface Options { url: string; }

const direct: Options = { url: "/x", timeout: 50 };   // compile error: 'timeout' is not in Options
const opts = { url: "/x", timeout: 50 };
const viaVariable: Options = opts;                    // accepted: not a fresh literal

A fresh object literal written directly where an Options is expected is checked for extra fields, as a typo guard. The same object held in a variable first is not.

Common mistakes

  • Reading an optional field without checking. b.subtitle.toUpperCase() throws at runtime when the field is absent.
  • Using || for defaults. It turns a real 0 or empty string into the default; test === undefined.
  • Forgetting the ?. Without it every object literal must supply the field.
  • Believing the interface checks data at runtime. It is erased, so a wrongly shaped value from outside still gets through.

Your exercise

Model an employee record with the pieces of this lesson. Write interface Person with a readonly id, a name and an optional nickname, then interface Employee extends Person that adds a team, and build one employee object from the input lines. The starter reads the input into the array lines: when the optional fourth line is missing, lines[3] is undefined, which is exactly what an absent optional field holds.

Then print two lines. For the first, use the fallback operator from the optional-fields section: a || b gives a unless it is falsy, and then b. The warning above about || was about values like 0 that are legitimately falsy; a nickname is text that is never meaningfully empty, so falling back to the full name is exactly right here. The second line is a template literal.

The interfaces are erased before the program runs, so the tests check the objects you build and what you print. The mistake the hidden tests catch is printing employee.nickname on its own, which writes the word undefined for an employee who has none.

Up nextConditionalsControl Flow

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…