Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Object Types: Properties, Optional and readonly Properties, Excess Property Checks

Learn to type objects in TypeScript: object type annotations, optional properties with ?, readonly properties, nested objects, methods in object types, excess property checking for object literals, and structural typing.

Beginner8 min readUpdated
On this page

Most data in JavaScript is objects: users, products, orders, settings. TypeScript describes an object's shape: which properties it has, and what type each one is.

Object type annotations#

TypeScript
const user: { name: string; age: number; email: string } = {
  name: "Asha",
  age: 21,
  email: "asha@example.com",
};

console.log(`${user.name} <${user.email}>`);
Output
Asha <asha@example.com>

Properties are separated by semicolons (commas also work). Writing the shape inline every time is repetitive, so in practice you give it a name with a type alias or interface:

TypeScript
type User = {
  name: string;
  age: number;
  email: string;
};

const ravi: User = { name: "Ravi", age: 31, email: "ravi@example.com" };
console.log(ravi.age + 1);
Output
32

The examples below use type declarations.

Missing and wrong properties#

TypeScript
type User = { name: string; age: number };
const meera: User = { name: "Meera" };
Compiler error
error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.
TypeScript
type User = { name: string; age: number };
const kiran: User = { name: "Kiran", age: "thirty" };
Compiler error
error TS2322: Type 'string' is not assignable to type 'number'.

Optional properties#

? marks a property that may be absent. Its type then includes undefined, so you must handle that case before using it:

TypeScript
type Product = {
  name: string;
  price: number;
  discountPercent?: number;
};

function finalPrice(p: Product): number {
  const discount = p.discountPercent ?? 0;
  return p.price - (p.price * discount) / 100;
}

console.log(finalPrice({ name: "Pen", price: 20 }));
console.log(finalPrice({ name: "Bag", price: 1500, discountPercent: 10 }));
Output
20
1350
TypeScript
type Product = { name: string; price: number; discountPercent?: number };
const bag: Product = { name: "Bag", price: 1500 };
console.log(bag.discountPercent.toFixed(1));
Compiler error
error TS18048: 'bag.discountPercent' is possibly 'undefined'.

readonly properties#

readonly stops a property being reassigned after the object is created:

TypeScript
type Order = {
  readonly id: number;
  status: string;
};

const order: Order = { id: 1042, status: "placed" };
order.status = "shipped";
order.id = 9999;
Compiler error
error TS2540: Cannot assign to 'id' because it is a read-only property.

readonly is only checked at compile time; it does not freeze the object at runtime (that is Object.freeze).

Nested objects and methods#

TypeScript
type Customer = {
  name: string;
  address: {
    city: string;
    pin: string;
  };
  greet(): string;
};

const c: Customer = {
  name: "Asha",
  address: { city: "Pune", pin: "411001" },
  greet() {
    return `Hi ${this.name} from ${this.address.city}`;
  },
};

console.log(c.greet());
Output
Hi Asha from Pune

Methods are written as name(params): returnType or as a property with a function type: greet: () => string.

Excess property checks#

When you write an object literal directly where a type is expected, TypeScript reports unknown properties, which catches typos:

TypeScript
type Settings = { theme: string; fontSize?: number };
const s: Settings = { theme: "dark", fontsize: 18 };
Compiler error
error TS2561: Object literal may only specify known properties, but 'fontsize' does not exist in type 'Settings'. Did you mean to write 'fontSize'?

Structural typing#

TypeScript compares objects by shape, not by name. Any object with the required properties fits, even if it has extra ones, as long as it is not a fresh literal (where the excess check above applies):

TypeScript
type Named = { name: string };

function hello(thing: Named): string {
  return `Hello, ${thing.name}`;
}

const student = { name: "Ravi", course: "TypeScript", year: 2 };
console.log(hello(student));   // extra properties are fine here
Output
Hello, Ravi

This is called structural typing (or "duck typing": if it has a name, it can be greeted). It is different from languages like Java, where types must be declared to implement each other.

Practice#

Practice exercise

Define a type Book with a readonly isbn string, a title, an optional subtitle, a price, and an authors array of strings. Write bookLabel(book: Book): string that returns "Title: Subtitle by A, B", leaving out the subtitle part when there is none.

Show solution
TypeScript
type Book = {
  readonly isbn: string;
  title: string;
  subtitle?: string;
  price: number;
  authors: string[];
};

function bookLabel(book: Book): string {
  const fullTitle = book.subtitle ? `${book.title}: ${book.subtitle}` : book.title;
  return `${fullTitle} by ${book.authors.join(", ")}`;
}

console.log(bookLabel({ isbn: "111", title: "Learning TS", price: 499, authors: ["Asha"] }));
console.log(bookLabel({ isbn: "222", title: "Web Basics", subtitle: "HTML and CSS", price: 299, authors: ["Ravi", "Meera"] }));
Output
Learning TS by Asha
Web Basics: HTML and CSS by Ravi, Meera

Key takeaway#

Finished this lesson?