Objects and unions

Describe an object shape

Name the properties an object must have and let TypeScript check object literals and property access.

Most of the values your programs pass around are objects. An object type describes the properties your code expects an object to have. You give the shape a name with type, then use that name wherever the object shows up:

type User = {
  id: number
  name: string
}

const user: User = { id: 1, name: 'Ada' }

Once the shape has a name, TypeScript checks every use of it. Leave out name and you get:

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

Add a property the type does not declare, like email, and you get:

error TS2353: Object literal may only specify known properties, and 'email' does not exist in type 'User'.

That second error is the excess property check. It only fires on an object literal assigned directly to the type. That is the moment you are most likely to typo a property name, so nmae gets caught right where you wrote it, when it is cheapest to fix.

Structural typing

TypeScript compares shapes, not names. This is structural typing. A value does not need to come from a User class or constructor. It only needs compatible properties. If a function somewhere returns { id: 7, name: 'Grace' }, that value is a valid User, wherever it came from.

This makes plain JavaScript objects easy to type. Here is a function that takes a User:

function printUser(user: User) {
  console.log(`${user.id}: ${user.name}`)
}

Inside the body, user.id is a number and user.name is a string. Type user.fullName by mistake and the compiler answers immediately:

error TS2339: Property 'fullName' does not exist on type 'User'.

In plain JavaScript that typo would print undefined and you would notice later, if at all.

What the type does not do

The User annotation on the parameter is a promise about how printUser() uses its input. It is not a check on where the input came from. If the object arrived through JSON.parse() or a network response, the annotation is a claim, not a verification. Later in the course we write real validation for uncertain data.

My advice is to keep object types focused on the contract you need. A giant type with every field the API can ever return makes functions harder to reuse and harder to test. If printUser() only reads id and name, a two-property type is the honest contract. Callers can pass bigger objects, thanks to structural typing, and the function only promises to touch those two fields.

Try this on your own: add an active: boolean property to User. Fix every error the compiler reports without using an assertion.

Lesson completed