Skip to content
The Switch · JavaScript to TypeScript · Lesson 3

Objects

A form with boxes, some optional, some in ink.

Contents
Part 1 of 7 · The hook

A form with boxes

An object has a shape. A type writes it down.

Picture a form at a clinic desk. It has boxes: Name, Age, Phone. The desk clerk doesn't accept a form with the Age box empty. Some boxes are marked "optional", like Email. And a few are printed in ink, not pencil: your patient number can't be changed once it is written.

A JavaScript object is that form, filled in: { name: "Asha", age: 21 }. The trouble is that JavaScript has no printed form. Nothing says which boxes must be filled, which are optional, or which are in ink. Any code can add a box, forget a box, or rub one out.

TypeScript lets you print the form. You write the shape of the object once: which properties it has, and what kind of value each one holds. Then every object that claims to be one of these is checked against it.

This lesson is the one you will use the most. Almost every real program is a pile of objects.

Remember: a type for an object is a printed form: the boxes, their kinds, which are optional, and which are in ink.

Part 2 of 7 · In JavaScript

A form with no form

Missing, misspelled and surprise fields all pass.

This function wants a user with a name and an age.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function describeUser(user) {  return user.name + " is " + user.age;}console.log(describeUser({ name: "Asha", age: 21 }));console.log(describeUser({ name: "Ravi" }));
Output
Asha is 21
Ravi is undefined

The second call forgot the age. JavaScript shrugged, and the sentence says "Ravi is undefined". Nothing warned us.

Typos in property names are quieter still.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const user = { name: "Asha", agee: 21 };console.log(user.age);
Output
undefined

We wrote agee by mistake. JavaScript added a new property called agee, and when we asked for age we got undefined. And an in-ink field, like a configuration value that must never change, can be changed by anyone.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const config = { host: "localhost", port: 8080 };config.port = 9090;console.log(config.port);
Output
9090

Remember: JavaScript objects have no required fields, no spell-check for property names, and nothing is protected.

Part 3 of 7 · In TypeScript

Print the form

Write the shape once. Get errors at every misuse.

Here is the same function with its form printed. On the TypeScript side, the shape is called User.

function describeUser(user) {  return user.name + " is " + user.age;}console.log(describeUser({ name: "Asha", age: 21 }));console.log(describeUser({ name: "Ravi" }));

Tinted lines are the ones that differ

The type User = { ... } part is a type alias: a name for a type, so you can use it again. It says a User has a name that is text and an age that is a number. The function now says user: User, and the second call is refused, because { name: "Ravi" } is missing a box. The error even names the missing property.

Extra fields get caught as well. This is the typo from before.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type User = {  name: string;  age: number;};const user: User = { name: "Asha", agee: 21 };

TypeScript says

error TS2561: Object literal may only specify known properties, but 'agee' does not exist in type 'User'. Did you mean to write 'age'?

When you write an object straight into a place that expects a User, TypeScript checks that it has the right properties and no unknown ones. It even suggests what you meant.

Now the optional box. A question mark after the name means "this property may be missing".

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Contact = {  name: string;  email?: string;};function emailOf(contact: Contact): string {  if (contact.email === undefined) {    return "no email on file";  }  return contact.email.toLowerCase();}

email?: string means the email is either text or not there at all. Watch what happens if you use it without checking first. In JavaScript, it crashes.

const user = { name: "Asha", age: 21 };console.log(user.email.toLowerCase());

Tinted lines are the ones that differ

The checker sees that user.email might be undefined, and refuses to let you call a method on it. The cure is a check, as in emailOf above: look first, then use. Lesson 6 covers this properly, along with the ?. and ?? shortcuts.

Finally, the ink. The word readonly in front of a property means "set it when you create the object, and never again".

const config = { host: "localhost", port: 8080 };config.port = 9090;console.log(config.port);

Tinted lines are the ones that differ

The assignment is refused. To get a changed copy, you build a new object rather than editing the old one, which the third practice problem asks you to do.

Remember: ? means "may be missing", readonly means "can't be changed", and everything else is required.

Part 4 of 7 · How it works

Shapes, not names

TypeScript compares what an object has.

Two things make objects in TypeScript click.

First: type or interface? There are two ways to name an object shape. They look alike, and for most code they do the same job.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Point = { x: number; y: number };interface Pin {  x: number;  y: number;}const corner: Point = { x: 1, y: 2 };const marker: Pin = corner; // the names differ, but the shape is the same

The two have the same shape, so one goes wherever the other is wanted. The differences are small. An interface can be extended with extends, and a type can be combined with &, which means "both of these".

TypeScriptOpen in TypeScript Playground (opens in a new tab)
interface Animal {  name: string;}interface Dog extends Animal {  breed: string;}type Cat = Animal & { indoor: boolean };const dog: Dog = { name: "Momo", breed: "Indie" };const cat: Cat = { name: "Mishti", indoor: true };

An interface can also be written twice with the same name, and TypeScript merges the two into one. A type can't. This is called declaration merging, and you rarely need it in everyday code.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
interface Settings {  theme: string;}interface Settings {  fontSize: number;}// Both declarations joined into one, so both fields are needed.const settings: Settings = { theme: "dark", fontSize: 16 };

A good rule: pick one and stay with it. This guide uses type, because it can also name things that aren't objects, which later lessons need. Many teams choose interface for objects. Either is a fine answer.

Second: TypeScript is structural. It decides whether an object fits a type by looking at what the object has, not at what it was called or where it came from. There is no need to say "this object implements that type". If it has the right properties, it fits.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
const pet = { name: "Momo", age: 3, tail: true };// pet has everything a User needs, and one extra. That is fine here,// because pet is a variable and not a fresh object literal.console.log(describeUser(pet));
Output
Momo is 3

pet has a name, an age and a tail. A User only needs a name and an age, so pet fits, and the extra tail is fine. Why was agee an error, then? The difference is a fresh object literal: an object written directly where a type is expected. Fresh literals get the strict extra-property check, because a stray property there is almost always a typo. A variable that already exists doesn't.

Remember: TypeScript asks "does it have what's needed?", not "what is it called?". Fresh object literals also get an extra check for unknown properties.

Part 5 of 7 · Watch out

Where objects bite

Three places the form can fool you.

Optional doesn't mean empty. In displayName, a nickname of "" (an empty string) is still a nickname. If you test it with a plain if (person.nickname), an empty one counts as missing. Compare against undefined when "not given" is what you mean.

readonly is shallow, and only for the checker. It stops assignments the checker sees. It doesn't freeze the object at runtime, and it doesn't reach inside a property that is itself an object. It is a promise to your teammates and your tools, not a lock.

A type doesn't check data that arrives while the program runs. If an object comes from a server, User is a hope, not a promise the data keeps. Lesson 0 showed this, and lesson 9 fixes it.

Also, don't reach for any or for a cast (writing as User to overrule the checker) when a shape fights you. Fix the shape, or fix the data. Lesson 11 says when a cast is fair.

Remember: readonly and ? are promises that the checker holds you to. They are gone when the program runs.

Part 6 of 7 · Practise

Three shapes, three jobs

Name the shape, make a box optional, protect the ink.

Each problem starts with a JavaScript object function. Open it in the Playground, and describe the shape.

Problem 1

Name the shape

Describe the object fullName expects, and use that type for its parameter. The call missing last should be refused. Remove it.

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function fullName(person) {  return person.first + " " + person.last;}console.log(fullName({ first: "Asha", last: "Rao" }));console.log(fullName({ first: "Ravi" })); // "Ravi undefined"

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Person = {  first: string;  last: string;};function fullName(person: Person): string {  return person.first + " " + person.last;}fullName({ first: "Asha", last: "Rao" }); // "Asha Rao"

The type lists exactly what the function reads: first and last, both strings. The function then asks for a Person. The call that forgot last is refused with 'Property last is missing', where JavaScript produced the text 'Ravi undefined'.

Problem 2

An optional nickname

Describe the person, with an optional nickname. displayName gives the nickname if there is one, and the first name if there isn't.

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function displayName(person) {  return person.nickname ? person.nickname : person.first;}console.log(displayName({ first: "Asha", nickname: "Ash" }));console.log(displayName({ first: "Ravi" }));

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Named = {  first: string;  nickname?: string;};function displayName(person: Named): string {  return person.nickname !== undefined ? person.nickname : person.first;}displayName({ first: "Ravi" }); // "Ravi"

nickname?: string makes the property optional, so both calls are fine. Comparing with undefined says exactly what we mean, which is 'was one given'. The earlier truthy check treated an empty nickname as missing, which is a quiet bug in the JavaScript.

Problem 3

Protect the defaults

withPort changes the config you give it, which also changes the shared defaults. Make the properties readonly, and return a new object instead.

Start here

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const defaults = { host: "localhost", port: 8080 };function withPort(config, port) {  config.port = port;  return config;}withPort(defaults, 9090);console.log(defaults.port); // 9090, so the defaults were changed behind our back

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
type Config = {  readonly host: string;  readonly port: number;};const defaults: Config = { host: "localhost", port: 8080 };function withPort(config: Config, port: number): Config {  return { ...config, port };}withPort(defaults, 9090); // a new object; defaults is untouched

readonly makes the assignment config.port = port an error, which is what pushes you to build a new object. The spread { ...config, port } copies every property and then replaces port. The defaults object stays untouched, which the JavaScript version did not manage.

Part 7 of 7 · Interview notes

Objects in the interview

Shapes, optional fields, and the type or interface question.

Expect the "type or interface" question in almost every TypeScript interview. The honest answer is short.

  • "What is the difference between type and interface?" For object shapes they are nearly the same. An interface can be extended with extends and merged by declaring it twice. A type can also name unions, tuples and other non-object types, and combines with &. Pick one and be consistent.
  • "What does ? after a property name mean?" The property is optional: it may be missing, so its type includes undefined.
  • "What does readonly do?" The checker refuses assignments to that property. It doesn't freeze the object at runtime.
  • "What is structural typing?" Types are compared by their shape, by what properties they have. Two types with the same shape are interchangeable, whatever they are called.
  • "Why is an extra property an error here but not there?" Object literals written directly where a type is expected get an excess-property check. An existing variable with extra properties is fine.
  • "Do types check data from an API?" No. Types are erased at runtime. Data from outside needs a runtime check.
  • Say it simply. "A type is the shape of an object. TypeScript checks shapes, not names" is a good answer.

Updated 2026-10-17

Made by VeraStack Labs

Part 1

The Switch

JavaScript to TypeScript