Chapter 2.4☕ 14 min read

Objects as Types (Inline)

Every object has a shape — TypeScript makes sure it fits

01The Biryani Box Shape

Most real-world data isn't a single string or number — it's a SHAPE. A user has a name, age, and email. A biryani has a type, price, and spice level. A product has an id, title, and inStock flag. TypeScript lets you describe the shape of an object inline — right where you use it.

Think of a biryani box from Paradise. The box has specific compartments — rice goes in the main section, raita in the small cup, salan in the other cup, and the kebab in the side slot. Each compartment has a NAME (the key) and can only hold a specific ITEM (the type). You can't put raita where the kebab goes, and you can't leave out the rice section — that would be an incomplete biryani box!

An inline object type describes this exact shape — what properties exist, what type each property is, and whether any are optional. This is the foundation of everything in TypeScript. Objects are the most common data structure you'll work with, and knowing how to describe their shapes is a skill you'll use every single day. Without object types, TypeScript would only catch mistakes with primitives — and most bugs live in objects.

When you define an inline object type, you're essentially drawing a blueprint. Any object that matches that blueprint — with all the right properties of the right types — passes the check. Objects with missing or wrong properties don't. It's that simple, and that powerful.

02Inline Type Syntax

The simplest way to type an object is inline — right in the parameter or return type. The curly braces with property:type pairs IS the type. There's no separate declaration needed.

function greet(user: { name: string; age: number }): string {
  return `Hello, ${user.name}! You are ${user.age}.`;
}

Multiple properties are separated by semicolons (or commas — both work). For example, a biryani object might look like this:

function printBiryani(item: { type: string; price: number; spicy: boolean }) {
  console.log(`${item.type} biryani — ₹${item.price}, Spicy: ${item.spicy}`);
}

Nested objects let you describe complex data — objects inside objects, each with their own shape:

function register(user: {
  name: string;
  address: { city: string; pin: number };
}) {
  console.log(`${user.name} from ${user.address.city}`);
}

Optional properties use ? — the property may or may not exist:

function contact(person: { name: string; phone?: string }) {
  // phone might be undefined — that's fine!
}

Read-only properties use readonly — the property can't be reassigned after creation, like a biryani order number:

function createOrder(order: { readonly id: number; item: string }) {
  // order.id = 999; // ERROR! Can't reassign readonly
}

Return types work the same way:

function getUser(): { name: string; age: number } {
  return { name: "Imran", age: 25 };
}

The key insight: TypeScript checks the SHAPE, not the variable name. You can pass any object that has at least the required properties with the right types. This is structural typing in action — if it looks like a duck and quacks like a duck, TypeScript accepts the duck.

03Excess Property Checking

When you pass an object LITERAL directly, TypeScript does a special check called "excess property checking." If you pass an object with EXTRA properties that don't exist in the type, TypeScript will ERROR.

function greet(user: { name: string }) {
  console.log(user.name);
}

// ❌ ERROR! 'age' is not expected in type '{ name: string }'
greet({ name: "Imran", age: 25 });

TypeScript is being strict here to catch typos and unintended properties. If you meant to type nme instead of name, this check saves you. It's a safety net for object literals.

BUT — and this is crucial — this only applies to object LITERALS passed directly. If you pass a variable instead, the extra properties are allowed:

const obj = { name: "Imran", age: 25 };
greet(obj); // ✅ Works! Extra 'age' is fine on a variable

This works because TypeScript uses "structural subtyping" — it checks if the object HAS the required shape, not if it has EXACTLY that shape. A variable with extra properties still satisfies the structural requirement because it contains everything needed.

This is one of the most confusing parts of TypeScript! The rule of thumb:

  • Literals → strict (excess properties cause errors)
  • Variables → flexible (extra properties are allowed)

If you want to explicitly allow extra properties, use an index signature:

function process(config: { [key: string]: unknown; name: string }) {
  // Now any extra string-keyed properties are fine
}

process({ name: "app", version: "1.0", debug: true }); // ✅ OK

The index signature [key: string]: unknown tells TypeScript: "this object has a name that's a string, and it might also have any other string-keyed properties." This is useful for configuration objects and dynamic data where you don't know all the keys upfront.

04Object Type Traps

Let's cover the most common mistakes developers make with inline object types. These traps will bite you if you're not careful!

Trap 1: Confusing Object Syntax with Destructuring

// This uses destructuring + inline type
function f({ name, age }: { name: string; age: number }) {
  console.log(name, age);
}

The destructuring { name, age } extracts properties, BUT the type annotation { name: string; age: number } still describes the whole object — not the extracted variables. The type always describes the shape of the argument, even when you destructure it.

Trap 2: Semicolons vs Commas

In object types, both work identically:

{ name: string; age: number }  // semicolons
{ name: string, age: number }  // commas

They're the same type! But pick one style and be consistent across your codebase. Semicolons are more common in TypeScript documentation.

Trap 3: Missing Required Property

function f(user: { name: string; age: number }) {}

f({ name: "Imran" }); // ❌ ERROR! 'age' is missing

Every required property must be present. If you want it optional, mark it with ?: { name: string; age?: number }.

Trap 4: Nested Types Getting HUGE

function f(user: {
  name: string;
  address: {
    city: string;
    state: string;
    pin: number;
    coords: { lat: number; lng: number };
  };
}) {}

This is unreadable! This is exactly why Type Aliases exist (coming in Chapter 2.5). They let you name a type and reuse it.

Trap 5: Method Properties in Object Types

You can include functions in object types using two syntaxes:

{ name: string; greet: () => string }   // arrow syntax
{ name: string; greet(): string }        // method syntax

Both are valid and mean the same thing. The method syntax is shorter, the arrow syntax is more explicit about it being a function type.

05Inline Objects Cheatsheet

Here's your complete cheatsheet for inline object types. Bookmark this — you'll come back to it often!

Basic Syntax

{ key: type; key: type }
// Example:
function f(user: { name: string; age: number }) {}

Optional Properties

{ name: string; age?: number }
// age may or may not exist — no error if missing

Readonly Properties

{ readonly id: number; name: string }
// id can be read but never reassigned

Nested Objects

{ user: { name: string; address: { city: string } } }
// objects within objects, each with their own shape

Method Properties

{ name: string; greet(): string }        // method syntax
{ name: string; greet: () => string }     // arrow syntax

Index Signature (Allow Extra Properties)

{ [key: string]: unknown; name: string }
// allows any extra string-keyed properties

Key Rules

  • Inline types are great for simple, one-time shapes — quick and convenient.
  • Excess property checking applies only to object literals — not variables.
  • Variables with extra properties pass structural typing — they have what's needed.
  • Use Type Aliases for complex or repeated shapes — your future self will thank you.
  • Every required property must be present — no shortcuts.

The Golden Rule: "Inline types are like a single-use biryani box — fine for one meal. But if you keep ordering the same box, give it a name!"

In the next chapter, we'll learn how to name our types with Type Aliases — so you never have to repeat a massive inline shape again.

Key Takeaways — Inline Object Types

  • Inline object types describe the SHAPE of an object — what keys exist and what types their values are.
  • Optional properties use ? and readonly properties use the readonly keyword.
  • Excess property checking is strict for literals but flexible for variables (structural subtyping).
  • Destructuring parameters still need the full object shape in the type annotation.
  • Use index signatures { [key: string]: unknown; name: string } to allow extra properties.
  • If your inline type gets huge, that's a sign you need a Type Alias (Chapter 2.5).
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase