Course outline · 0% complete

0/26 lessons0%

Course overview →

Naming Shapes with Interfaces

lesson 3-1 · ~10 min · 8/26

Lesson 2-2 typed an object inline as { name: string; age: number }, and it is worth naming the drawback of repeating that inline type across five different functions.

The cost is duplication. Changing the shape means editing five places, and any one of them can be missed. The shape also has no name, so there is no word for it in conversation, in error messages, or in an import.

Notice what the cost is not. Types are erased at runtime, so repeating one has no effect on speed or bundle size. The problem is purely maintenance, and giving the shape one name is exactly what interfaces are for.

interface: declare a shape once

An interface gives an object shape a name you can reuse:

interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return "Hello, " + user.name;
}

This is the inline object type from lesson 2-2, extracted and named. Every function that works with users now says User, and the shape lives in exactly one place.

TypeScript checks interfaces structurally: any object with a name string and an age number counts as a User, whether or not it was declared with that intent. The shape is the contract, not the label. This is called structural typing, and it is different from Java-style languages where names matter.

An interface used by a function and two callers

interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return "Hello, " + user.name + " (" + user.age + ")";
}

const ada: User = { name: "Ada", age: 36 };
console.log(greet(ada));
console.log(greet({ name: "Grace", age: 45 }));

Output

Hello, Ada (36)
Hello, Grace (45)

The second call passes an object literal directly, without a named variable or any mention of User. It type-checks because it matches the shape, which is structural typing in action.

Removing the age property from that literal produces a precise error: Property 'age' is missing in type '{ name: string; }' but required in type 'User'. Read that message carefully, because you will see its shape constantly. It names the type you supplied, the type that was required, and the exact property that broke the match.

describeSong: an interface with three properties

Song names a shape with a title, an artist, and a duration in seconds, and describeSong turns any Song into a sentence.

interface Song {
  title: string;
  artist: string;
  seconds: number;
}

function describeSong(song: Song): string {
  return song.title + " by " + song.artist + " (" + song.seconds + "s)";
}

console.log(describeSong({ title: "Hey Jude", artist: "The Beatles", seconds: 431 }));

Output

Hey Jude by The Beatles (431s)

Reading the declaration

  • Interface bodies list properties just as an object type does: title: string; on its own line, one per property, semicolons between them.
  • The return string is built with + exactly as in lesson 1-3's describePet. Nothing about interfaces changes how you write the body.
  • The single parameter is annotated Song rather than the full braced shape, which is the entire point. If the song shape later gains a year, only the interface changes, and every function that mentions Song picks it up.