RizTech Academy logo
RizTech Academy
Objects, Interfaces and Type AliasesLesson 1 of 520 min

Typing objects

Most data in a real program is objects — a user, an order, a product — each a bundle of named properties. Describing the shape of an object is the heart of everyday TypeScript, and it is what the next several lessons are about. This one is the object type itself: the notation, and the two surprising rules (excess-property checking and structural compatibility) that trip people up.

Describing an object's shape

You type an object by listing its properties and their types, in braces:

let member: { name: string; age: number; city: string } = {
  name: "Kavita",
  age: 33,
  city: "Pune",
};

The type { name: string; age: number; city: string } says "an object with exactly these three properties, of these types". TypeScript now checks every use:

member.name.toUpperCase();     // fine — name is a string
member.age.toFixed();          // fine — age is a number
member.email;                  // error: Property 'email' does not exist on this type
member.age = "old";            // error: Type 'string' is not assignable to type 'number'

You cannot read a property that is not in the type, and you cannot assign the wrong type to one. The misspelled-property and wrong-type bugs from lesson one are gone.

Inline object types like this get unwieldy fast, which is why you almost always give the shape a name — with an interface or a type alias, the next two lessons. But the inline form is worth knowing, because you will see it for small, one-off shapes and for function parameters.

Inference builds object types for you

You rarely write these annotations, because TypeScript infers the shape from the value:

const book = { title: "Sairat", price: 300, inStock: true };
// inferred: { title: string; price: number; inStock: boolean }

book.title.toUpperCase();      // fine
book.pages;                    // error: Property 'pages' does not exist

Create an object literal and TypeScript reads its shape and checks every access against it, no annotation needed. As always, you annotate the boundaries — a function parameter that takes an object, or external data — and let inference handle objects you build locally.

Excess-property checking — the surprising strictness

Here is the first thing that surprises people, and it seems to contradict structural typing (from the how-checking-works lesson). When you pass an object literal directly to something expecting a type, TypeScript is strict about extra properties:

type Member = { name: string; city: string };

const m: Member = { name: "Kavita", city: "Pune", age: 33 };
// error: Object literal may only specify known properties, and 'age' does not exist in type 'Member'

age is an extra property, and TypeScript rejects the literal — even though structurally it has everything Member needs. This is excess-property checking, and it exists to catch a real class of bug: a typo in a property name.

const m2: Member = { name: "Kavita", citty: "Pune" };
// error: 'citty' does not exist... Did you mean 'city'?

Without excess-property checking, citty would be treated as a harmless extra property and city would be silently missing — a classic bug. The strict check on literals catches your typos.

The subtlety: this strictness applies only to object literals passed directly. Assign through a variable first, and structural typing takes over (extra properties allowed):

const raw = { name: "Kavita", city: "Pune", age: 33 };
const m3: Member = raw;        // fine! raw is a variable, not a literal — extras allowed

So the rule is: a direct object literal is checked strictly for excess properties; a value assigned through a variable is checked structurally (extras allowed). This looks inconsistent until you see the reasoning — the strict check is specifically a typo-catcher for the common case of writing a literal, and it deliberately does not apply once the value has "escaped" into a variable where it might legitimately carry more.

Object types are structural

Beyond the literal case, objects follow the structural typing from the earlier lesson — compatibility is about shape, not name:

type Named = { name: string };
function greet(x: Named) { return `Namaste, ${x.name}`; }

const member = { name: "Kavita", age: 33 };
greet(member);       // fine — member has a name; the extra age is allowed (variable, not literal)

greet wants something with a name; member has one (and more); it fits. This is why TypeScript feels lightweight — you do not formally declare that member "is a" Named; if the shape matches, it works.

An index signature — objects with dynamic keys

Sometimes you do not know the property names in advance — a dictionary from string keys to values. An index signature types this:

type Prices = { [item: string]: number };

const menu: Prices = { chai: 15, samosa: 20, vadaPav: 25 };
menu.chai;             // number
menu.anything;         // number (TypeScript assumes any string key maps to a number)

{ [item: string]: number } means "any string key maps to a number". Use it for genuine dictionaries where the keys are dynamic. But prefer a Record (a utility type covered later, Record<string, number> means the same thing more readably) or, better still, a Map when you have a real dynamic collection — and use a fixed object type when you actually know the property names. An index signature is a tool for the specific case of unknown keys, not a lazy way to avoid naming your properties.

Check your work

How to type an object. List its properties and their types in braces: { name: string; age: number }.

What TypeScript checks on an object. No reading a property not in the type, and no assigning the wrong type to one.

How object types are usually created. By inference from the value, or by naming the shape with an interface/type alias (next lessons).

Excess-property checking. A direct object literal is rejected if it has properties not in the target type — a typo-catcher.

Why it exists. To catch a misspelled property (citty for city) that would otherwise be a silent extra plus a missing required property.

When excess-property checking does not apply. When the value is assigned through a variable first — then structural typing allows extras.

Structural typing for objects. Compatibility is by shape; a value fits if it has at least the required properties.

What an index signature is for. Objects with dynamic, unknown keys — { [k: string]: number } — though a Record or Map is often better.

Practice

  1. Type an object with three properties. Access a property not in the type and assign a wrong type to one; read both errors.
  2. Create an object literal and confirm its shape is inferred (hover). Access a non-existent property and read the error.
  3. Assign an object literal with an extra property to a named type. Read the excess-property error. Note the "Did you mean...?" suggestion for a typo.
  4. Assign the same object through a variable first, and confirm the extra is now allowed. Explain the difference.
  5. Write a greet(x: { name: string }) and pass an object with more properties. Confirm it fits.
  6. Type a dictionary with an index signature { [k: string]: number }. Access a key you never set and note its type.
  7. Decide, for a case you know, whether an index signature, a Record, a Map, or a fixed object type is the right tool.

Official documentation

Next: interfaces.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship