Object Types

TypeScript · Lesson 7 of 28 6 min read Updated

Most data in real programs comes as objects: a student with a name and roll number, a product with a price, an order with a list of items. An object type describes the shape of such an object: which properties it has and what type each one is.

In this lesson you will write object types, make properties optional or read-only, nest objects inside objects, describe dictionaries with index signatures, and learn how TypeScript decides whether an object "fits" a type.

Describing an object

You can write an object type inline, but it is usually better to give it a name with a type alias. A type alias is created with the type keyword and can be used anywhere a type is expected.

TypeScriptRun it yourself
type Product = {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
};

const bag: Product = { id: 101, name: "School bag", price: 1299, inStock: true };
const bottle: Product = { id: 102, name: "Steel bottle", price: 449, inStock: false };

function label(p: Product): string {
  const status = p.inStock ? "available" : "out of stock";
  return `#${p.id} ${p.name}, ₹${p.price} (${status})`;
}

console.log(label(bag));
console.log(label(bottle));
Output
#101 School bag, ₹1299 (available)
#102 Steel bottle, ₹449 (out of stock)

Properties are separated by semicolons (commas also work). Type names use PascalCase by convention: Product, StudentRecord, TrainTicket.

TypeScript now checks every object that claims to be a Product:

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

const pen: Product = { id: 1, name: "Gel pen" };
const pencil: Product = { id: 2, name: "Pencil", price: 10, colour: "red" };
const eraser: Product = { id: 3, name: "Eraser", price: "5" };
console.log(pen.cost);
Output
app.ts(7,7): error TS2741: Property 'price' is missing in type '{ id: number; name: string; }' but required in type 'Product'.
app.ts(8,61): error TS2353: Object literal may only specify known properties, and 'colour' does not exist in type 'Product'.
app.ts(9,50): error TS2322: Type 'string' is not assignable to type 'number'.
app.ts(10,17): error TS2339: Property 'cost' does not exist on type 'Product'.

Four different mistakes, four clear messages: a missing property, an unknown extra property (often a typo), a wrong type, and reading a property that does not exist.

Optional and readonly properties

Put ? after a property name to make it optional: the object may leave it out. Put readonly in front to stop code from changing it after the object is created.

TypeScriptRun it yourself
type Student = {
  readonly rollNo: number;
  name: string;
  email?: string;
  guardian?: {
    name: string;
    phone: string;
  };
};

const aarav: Student = { rollNo: 14, name: "Aarav" };
const isha: Student = {
  rollNo: 15,
  name: "Isha",
  email: "isha@example.com",
  guardian: { name: "Sunita", phone: "98765 43210" },
};

for (const s of [aarav, isha]) {
  const email = s.email ?? "no email";
  const phone = s.guardian?.phone ?? "no guardian phone";
  console.log(`${s.rollNo} ${s.name}: ${email}, ${phone}`);
}

isha.name = "Isha Verma"; // allowed
console.log(isha.name);
Output
14 Aarav: no email, no guardian phone
15 Isha: isha@example.com, 98765 43210
Isha Verma

An optional property has the type string | undefined when you read it, so TypeScript makes you handle the missing case. The optional chaining operator ?. and the nullish coalescing operator ?? from JavaScript are perfect for this. Writing isha.rollNo = 99 would be a compile error, because rollNo is readonly. Like readonly arrays, this is a compile-time check only.

The guardian property shows a nested object type. For bigger shapes, give the inner type its own name (type Guardian = {...}) and use guardian?: Guardian. Small named types are easier to reuse and read.

Structural typing

TypeScript compares objects by their shape, not by their name. If an object has all the properties a type needs, with the right types, it fits. This is called structural typing (some people call it "duck typing": if it walks like a duck and quacks like a duck, it is a duck).

TypeScriptRun it yourself
type HasLocation = { city: string; pincode: number };

function deliveryZone(place: HasLocation): string {
  return place.pincode >= 400000 && place.pincode < 500000 ? "West" : "Other";
}

const shop = { name: "Meera Stores", city: "Pune", pincode: 411001, rating: 4.6 };
const customer = { name: "Rohan", city: "Lucknow", pincode: 226001 };

console.log(deliveryZone(shop));
console.log(deliveryZone(customer));
Output
West
Other

Neither shop nor customer was declared as HasLocation, and both have extra properties, yet both are accepted because they contain city and pincode. The extra-property error you saw earlier only happens when you write an object literal directly where a type is expected, because there an extra property is almost always a typo.

Index signatures and Record

Sometimes you do not know the property names in advance, for example marks by subject or stock by product code. An index signature describes "any number of keys of this type, each with a value of that type". The built-in Record<K, V> type says the same thing more briefly.

TypeScriptRun it yourself
type MarksBySubject = { [subject: string]: number };
type StockByCode = Record<string, number>;

const marks: MarksBySubject = { maths: 91, physics: 84, hindi: 78 };
const stock: StockByCode = { "BAG-01": 12, "BTL-07": 0 };

marks.chemistry = 88;

let total = 0;
for (const [subject, score] of Object.entries(marks)) {
  console.log(subject.padEnd(10), score);
  total += score;
}
console.log("Total:", total);

const outOfStock = Object.keys(stock).filter((code) => stock[code] === 0);
console.log(outOfStock);
Output
maths      91
physics    84
hindi      78
chemistry  88
Total: 341
[ 'BTL-07' ]

Be careful: with an index signature, marks.biology has the type number even though it is undefined at run time. The noUncheckedIndexedAccess option adds | undefined to such reads so you remember to check. When the keys are a known, fixed set, use a union of literal types instead, which you will learn in Literal Types, as const & satisfies.

Common mistakes

  • Confusing email?: string with email: string | undefined. The first lets you leave the property out; the second requires the property to be written, even if its value is undefined.
  • Using object or {} as a type. These accept almost anything and tell TypeScript nothing about the properties. Describe the real shape.
  • Fighting the excess property error. It usually means a typo, such as colour instead of color. Fix the name instead of adding a workaround.
  • Huge inline types. If you write the same object shape twice, give it a name with type.

Key points

  • An object type lists property names and their types; name it with type Name = { ... }.
  • prop?: makes a property optional, and readonly stops reassignment at compile time.
  • Use ?. and ?? to read optional and nested properties safely.
  • TypeScript uses structural typing: any object with the required properties fits, but object literals are checked for extra properties.
  • Index signatures and Record<K, V> describe objects with keys you do not know in advance.

Exercise

Create a type Order for a food delivery app in Kochi with: a read-only orderId (number), customer (string), items (an array of objects with name, price and qty), an optional couponCode (string) and address (an object with line1 and pincode). Write a function orderTotal(order: Order): number that adds price * qty for all items and takes ₹50 off when a coupon code is present. Test it with one order with a coupon and one without.

Show solution
TypeScript
type OrderItem = { name: string; price: number; qty: number };

type Order = {
  readonly orderId: number;
  customer: string;
  items: OrderItem[];
  couponCode?: string;
  address: { line1: string; pincode: number };
};

function orderTotal(order: Order): number {
  const sum = order.items.reduce((total, item) => total + item.price * item.qty, 0);
  return order.couponCode ? sum - 50 : sum;
}

const first: Order = {
  orderId: 5001,
  customer: "Farhan",
  items: [{ name: "Appam", price: 60, qty: 4 }, { name: "Stew", price: 180, qty: 1 }],
  couponCode: "KOCHI50",
  address: { line1: "MG Road", pincode: 682016 },
};
const second: Order = {
  orderId: 5002,
  customer: "Ananya",
  items: [{ name: "Puttu", price: 70, qty: 6 }],
  address: { line1: "Marine Drive", pincode: 682031 },
};

console.log(orderTotal(first));  // 370
console.log(orderTotal(second)); // 420

Giving the item shape its own name, OrderItem, keeps Order short and lets you reuse the item type elsewhere.

Quick Quiz

1. What does email?: string mean in an object type?

2. Why does const p: Product = { id: 1, name: "Pen", price: 10, colour: "red" } give an error?

3. An object shop has name, city and pincode. Can you pass it to a function that needs { city: string; pincode: number }?

4. Which type describes an object whose keys are any strings and whose values are numbers?

Learn TypeScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call