Interfaces

A named description of what properties an object must have, and what type each one holds.

What is it?

A single string or number type is enough for a simple value, but most real data is an object with several properties — a user with a name, an email, and an age, say. Writing that shape out inline everywhere you use it gets repetitive fast, and there's nothing tying separate inline descriptions together as "the same shape."

An interface solves this by giving a shape a name, once, in one place. You declare what properties an object of this shape must have and what type each one is, and then you reuse that name everywhere instead of repeating the shape. If an object is missing a required property, has one with the wrong type, or is passed somewhere expecting that shape when it doesn't match, TypeScript flags it immediately.

Explain like I'm 10

An interface is like a form template with labeled blanks: "Name: ___, Email: ___, Age: ___." Anyone filling it out must provide all the required blanks with the right kind of answer — you can't hand in a form missing the email field, or write your age as "twenty" instead of a number.

Examples

Defining and using an interface

interface User {
  name: string;
  email: string;
  age: number;
}

const user: User = {
  name: "Kai",
  email: "kai@example.com",
  age: 27,
};

function greet(user: User): string {
  return `Hello, ${user.name}!`;
}

The User interface describes the required shape once. Both the user variable and the greet function reuse it, so any object claimed to be a User is checked against the exact same definition.

Optional properties and readonly

interface Product {
  readonly id: string;
  name: string;
  discountCode?: string; // optional — may or may not be present
}

const item: Product = { id: "p-1", name: "Mug" }; // valid, discountCode omitted

item.id = "p-2";
// Error: Cannot assign to 'id' because it is a read-only property.

A ? after a property name marks it optional, so objects without it are still valid. readonly allows a property to be set once but never reassigned afterward.

How it works

An interface itself produces no JavaScript at all — it exists purely at compile time, as a description the compiler checks object literals, function parameters, and variables against. When you annotate something with an interface name, TypeScript compares every property on the actual value to every property the interface requires, flags anything missing or mismatched, and then discards the interface entirely once compilation finishes — it leaves no trace in the compiled .js output.

Why does it exist?

Without a name for a shape, every function that expects "an object with a name, email, and age" would have to spell that shape out inline, and there'd be no guarantee that two inline descriptions actually mean the same thing. Interfaces give a shape a single, reusable, checkable name, which makes intent clearer and mistakes far easier to catch consistently across a whole codebase.

When to use it

Use an interface any time you're describing the shape of an object — function parameters that are objects, the data returned from an API, the props a component accepts, or any structure you'll reuse in more than one place.

When not to use it

For a type that isn't fundamentally an object shape — a union of specific string values, a function type, or a primitive alias — a type alias (covered next) is usually the more natural fit. Interfaces are specifically suited to describing "objects with named properties."

Common mistakes

  • Forgetting the ? on a property that's genuinely optional, forcing every object to include it even when it isn't always relevant.

  • Assuming readonly protects the object at runtime — it's a compile-time-only check; plain JavaScript (or a type-unsafe cast) can still change the value.

  • Defining the same object shape inline in multiple places instead of naming it once as an interface and reusing it.

Practice exercises

  1. Easy:

    Write an interface Book with title: string, author: string, and pages: number, then create one object that satisfies it.

  2. Medium:

    Add an optional property isbn?: string to Book, and create two valid Book objects — one with an ISBN and one without.

  3. Hard:

    Write a function printSummary(book: Book): string that returns a formatted string, and add a readonly property to Book that would trigger a compiler error if the function tried to modify it.

Interview questions

What is an interface used for in TypeScript?

Naming and reusing the description of an object's shape — which properties it must have and what type each one is — so the compiler can check objects against it consistently.

Does an optional property (`prop?: type`) allow the property to be `undefined`, or does it allow the property to be omitted entirely?

Both — it can be omitted from the object altogether, or included and explicitly set to undefined.

Do interfaces exist at runtime in the compiled JavaScript?

No. Interfaces are purely a compile-time construct used for type checking; they produce no runtime code at all.

What is *structural typing*, and how does it relate to how interfaces are checked?

TypeScript compares types by their shape (which properties exist and what types they are), not by name or declared intent. An interface is checked structurally: any object with the right properties satisfies it, whether or not that object was ever explicitly annotated with the interface's name.

Given `interface User { name: string } const obj = { name: "Kai", age: 5 }; const u: User = obj;`, does this compile, even though `obj` has an extra `age` property?

Yes. Because obj is assigned through an existing variable rather than written as a fresh object literal at the assignment site, TypeScript only checks that it has at least the properties User requires — extra properties on an already-existing object are allowed.

Why does `const u: User = { name: "Kai", age: 5 };` fail to compile, when assigning the equivalent object through a variable (as in the previous question) does not?

This is TypeScript's excess property check: object literals written directly at the point of assignment are checked more strictly, because an extra property on a fresh literal is very often a typo or a misunderstanding of the target shape. That stricter check doesn't apply once the object has already been assigned to a differently-typed variable first.

Does marking a property `readonly` on an interface prevent it from ever being changed, even by plain JavaScript or a type assertion?

No — readonly is a compile-time-only check. TypeScript refuses to compile code that reassigns the property directly, but a type assertion or code that isn't type-checked at all can still mutate it at runtime, since the restriction leaves no trace in the compiled JavaScript.

If `discountCode?: string` is declared on an interface, what is its type inside code that has already confirmed the object has it?

string — TypeScript narrows string | undefined down to string once you've checked (for example with if (item.discountCode)) that the property is actually present, since the optional marker really means "string, or absent/undefined."

Can two objects that were never declared with the same interface name still both satisfy that interface?

Yes — because interfaces check structure, not declared identity, any object with matching property names and types satisfies the interface regardless of how it was created or what (if anything) it was originally annotated as.

What happens if you declare `interface User { name: string }` a second time later in the same scope, adding `interface User { age: number }`?

TypeScript merges the two declarations into a single interface with both name and age — this is called declaration merging, and it's a feature unique to interfaces; a type alias with a duplicate name would instead be a compile error.

How would you make one interface build on another, like an `Admin` that has everything a `User` has plus a `permissions` field?

With interface Admin extends User { permissions: string[] } — extends copies in every property from User, and any object satisfying Admin must then satisfy all of User's requirements plus the new one.

Can an interface describe a property whose key names aren't known in advance, like an object being used as a dictionary?

Yes, with an index signature: interface Scores { [username: string]: number } describes an object where every key is a string and every value is a number, regardless of how many keys exist or what they're named.

Can an interface property hold a function, and if so, how is it typed?

Yes — for example interface Logger { log: (message: string) => void } describes a property that must be a function accepting a string and returning nothing. TypeScript checks any function assigned to log against that exact signature.

If an interface property's type is itself another interface, like `interface Order { customer: User }`, how deep does TypeScript's checking go?

All the way down — TypeScript recursively checks nested object shapes, so an Order object's customer property must itself satisfy every requirement of User, not just be present.

Why does defining the same object shape inline in several different function signatures tend to cause problems over time?

There's nothing tying the separate inline descriptions together — if the shape needs to change, such as adding a required field, you have to find and update every inline occurrence by hand, and it's easy to miss one or let them silently drift out of sync. Naming the shape once as an interface and reusing it means there's exactly one place to update.

Is it valid to have a required property listed after an optional one in an interface, like `interface Item { discount?: number; name: string }`?

Yes — unlike function parameters, interface property order doesn't matter for validity. Properties are matched by name, not position, so optional and required properties can be declared in any order.

What would happen if you forgot the `?` on a property that's genuinely sometimes absent, like writing `discountCode: string` instead of `discountCode?: string`?

Every object typed as that interface would be required to include discountCode, so any real object that legitimately doesn't have a discount code would fail to satisfy the interface — forcing callers to invent a placeholder value just to satisfy the compiler.

When would a type alias be a more natural fit than an interface for describing something?

When the thing you're naming isn't fundamentally "an object with named properties" — a union of specific allowed values, a tuple, or a function signature are all things a type alias can express directly that an interface cannot.

Does an interface itself appear anywhere in the compiled `.js` output?

No — like all TypeScript-only constructs, an interface is used purely to check code during compilation and is discarded entirely once compilation finishes; there is no runtime object, class, or value corresponding to it.

If a function parameter is typed with an interface, what happens if you call the function with an object missing one required property?

TypeScript reports a compile error at the call site, naming the missing property — the function is never actually invoked with the incomplete object, since the mismatch is caught before the code runs.

Scenario: you want a `Config` interface where `apiUrl` is required but `timeout` should fall back to a sensible number if the caller doesn't provide it. Does the interface itself supply that fallback?

No — an interface (with timeout?: number) only describes that the property may be absent; it doesn't supply a default value. Supplying the fallback, such as config.timeout ?? 5000, is separate logic you still have to write in the code that consumes the object.

Why are interfaces particularly well suited to describing the props a UI component accepts?

Component props are almost always a plain object with a fixed, named set of fields, some required and some optional — exactly the shape interfaces are designed to describe and enforce, catching a missing or mistyped prop at the call site instead of at render time.