Start learning free

LESSON 153 OF 348 · TYPESCRIPT ESSENTIALS

Interfaces in TypeScript: Describing the Shape of an Object

Lesson Read the full explanation

TL ; DR:

An interface gives a name to the shape of an object: which properties it must have, and what type each one is.

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

From then on, User works anywhere a type works — in variable annotations, in function parameters, everywhere.



Full lesson:

So far, your type annotations described single values: let age: number = 31;. But real programs pass whole objects around — a user, a product, a point on a map. An interface describes such an object once, so the compiler can check every place it is used.


Using an interface:

You attach an interface to an object with the usual colon annotation, and you can use it as a function parameter type too:

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

function greet(user: User): void {
    console.log("Hello, " + user.name + "!");
}

const alice: User = { name: "Alice", age: 30 };
greet(alice);

The output of this code is:

Hello, Alice!

Inside greet, the compiler knows user has a name and an age — autocomplete works, typos are caught, and nothing is ever mysteriously undefined.

Note that an interface exists only at compile time. It produces no JavaScript at all — it is purely a contract the compiler enforces while you write the code.


The trap — the shape must match exactly:

An object annotated with an interface must have every declared property. Leave one out:

const bob: User = { name: "Bob" };

and the compiler refuses:

error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.

And when you assign an object literal directly, TypeScript also runs an excess property check — properties the interface does not mention are rejected too:

const eve: User = { name: "Eve", age: 22, city: "Paris" };
error TS2353: Object literal may only specify known properties, and 'city' does not exist in type 'User'.

Read these two messages carefully — they name the missing or extra property and the interface it clashes with. Fixing them is exactly how interface bugs get solved in real projects.


Assignment
Follow the Coding Tutorial and let's put an interface to work:

  • Declare an interface User with name: string and age: number.
  • Define a function describe(user: User): void that prints "name = " + user.name and, on the next line, "age = " + user.age.
  • Create a constant ada of type User with name "Ada" and age 36.
  • Call describe(ada).

The expected output is:

name = Ada
age = 36

Hint
Look at the examples above if you get stuck.


STEP 1 OF 8
    Ctrl + Enter to check

    TYPESCRIPT · TYPESCRIPT ESSENTIALS

    Ts Interfaces in TypeScript, explained

    What an interface is

    An interface gives a name to the shape of an object: which properties it has and what type each one holds. You write it once, and from then on the name works anywhere a type works: on variables, on function parameters and on return types.

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

    Until now, annotations described single values, as in let age: number = 31;. Real programs pass whole objects around, such as a user, a product or a point on a map. Without an interface, every function that receives such an object would have to be trusted to get the property names and types right. With an interface, the compiler checks each place that touches the object.

    Using an interface

    You attach the interface with the usual colon annotation, and you can use it as a parameter type:

    interface User {
        name: string;
        age: number;
    }
    
    function greet(user: User): void {
        console.log("Hello, " + user.name + "!");
    }
    
    const alice: User = { name: "Alice", age: 30 };
    greet(alice);
    

    This prints:

    Hello, Alice!
    

    Inside greet, the compiler knows that user has a name and an age. Your editor can suggest those property names, and a typo such as user.nmae is reported as an error before the program runs.

    An interface exists only for the compiler. When TypeScript compiles to JavaScript, the interface block is removed completely and produces no code. Nothing checks the shape of an object while the program runs.

    The shape must match

    An object annotated with an interface must have every declared property. If one is missing, the compiler reports it:

    const bob: User = { name: "Bob" };
    
    error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.
    

    When you assign an object literal directly, TypeScript also runs an excess property check: properties the interface does not mention are rejected too.

    const eve: User = { name: "Eve", age: 22, city: "Paris" };
    
    error TS2353: Object literal may only specify known properties, and 'city' does not exist in type 'User'.
    

    Both messages name the property and the interface involved, so reading them tells you what to add or remove. That is the normal way interface mistakes get fixed.

    The lesson's exercise, step by step

    The goal is a program that prints two lines, name = Ada and age = 36.

    1. Declare the interface. Write interface User with a body in braces. The interface is the contract that every User has to follow.
    2. Add the name field. Inside the braces, write name: string;. A field is a property name, a colon and a type, with no value.
    3. Add the age field. In the same way, age: number;.
    4. Define the function. describe takes one parameter user of type User and returns void, which means it returns nothing.
    5. Print the name. Inside describe, use console.log("name = " + user.name). Because user is a User, the compiler guarantees that the field exists.
    6. Print the age. On the next line, console.log("age = " + user.age). The + turns the number into text and joins it to the label.
    7. Create the object. Declare const ada: User and set it to an object literal with the name "Ada" and the age 36. The annotation makes TypeScript check the literal immediately, so a missing or extra property is a compile error.
    8. Call the function. describe(ada) prints both lines.

    The complete program:

    interface User {
        name: string;
        age: number;
    }
    function describe(user: User): void {
        console.log("name = " + user.name);
        console.log("age = " + user.age);
    }
    const ada: User = { name: "Ada", age: 36 };
    describe(ada);
    

    Its output:

    name = Ada
    age = 36
    

    Gotchas

    • Separators. Fields inside an interface may end with ; or ,. Both are accepted, and semicolons are the common style. Object literals use commas.
    • Passing a bad value still runs. The compiler reports type errors, but it still writes the JavaScript, and the lessons' tool runs it. Calling describe({ name: "Q" }) gets an error from the compiler, yet the program prints name = Q and age = undefined. Treat the red error as a bug to fix, not a warning.
    • Casts do not convert anything. JSON.parse('{"name":"Zed"}') as User compiles without complaint, although the object has no age. The cast only tells the compiler to trust you, and user.age is undefined at run time. Data from outside the program, such as JSON, is not checked by the interface.
    • The excess property check applies to literals. If you first store { name: "Kim", age: 5, city: "Rome" } in a variable and then pass the variable to describe, there is no error. The object has everything a User needs, and extra properties are allowed when the value is not a fresh literal.
    • Optional and read-only fields. A property written as y?: number may be left out, and readonly id: string can be set when the object is created but not assigned to later. Assigning to it is a compile error.
    • Interfaces describe objects, not values. You can't write User.name in a program. There is nothing at run time to read, and typeof ada is just "object".

    Key takeaways

    • An interface names an object shape: property names with their types.
    • Annotating a variable or parameter with it makes the compiler check every use.
    • Missing properties and, for fresh object literals, extra properties are compile errors.
    • Interfaces are erased when compiling, so they never check data at run time.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free