Object Types
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.
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));#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:
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);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.
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);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).
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));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.
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);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?: stringwithemail: string | undefined. The first lets you leave the property out; the second requires the property to be written, even if its value isundefined. - Using
objector{}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
colourinstead ofcolor. 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, andreadonlystops 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
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)); // 420Giving 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?
The ? makes the property optional, so its type when read is string | undefined.
2. Why does const p: Product = { id: 1, name: "Pen", price: 10, colour: "red" } give an error?
Excess property checking catches likely typos in object literals.
3. An object shop has name, city and pincode. Can you pass it to a function that needs { city: string; pincode: number }?
TypeScript uses structural typing: the shape matters, not the name.
4. Which type describes an object whose keys are any strings and whose values are numbers?
Record<string, number> is the same as the index signature { [key: string]: number }.
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
