Interfaces in TypeScript: Describing the Shape of Your Objects

Learn how TypeScript interfaces describe the shape of an object, plus optional and readonly properties and how to extend one interface from another, with clear beginner examples.

Published September 20, 20268 min readBy ACY Partner Indonesia
Interfaces in TypeScript cover with code chip interface User
300 × 250Ad Space AvailablePlace your ad here

Most of the data you work with in real programs isn’t a single number or word. It’s an object: a customer with a name and an email, a product with a price and a label, a settings record with a handful of flags. The moment your code starts passing these objects around, you want a way to say “this thing should look like this.” That description is exactly what an interface gives you.

In TypeScript, an interface is a named blueprint for the shape of an object. It lists which properties the object has and what type each one should be. You write the blueprint once, attach it wherever you need it, and TypeScript quietly checks that every object actually matches. It’s the everyday workhorse for typing objects, and once it clicks you’ll reach for it constantly.

What “the shape of an object” means

Before any TypeScript, let’s be precise about the word shape. When we talk about the shape of an object, we mean two things together: the names of its properties and the type of value stored under each name.

Take a plain object like this:

const user = {
  name: "John Doe",
  email: "john@example.com",
  age: 34,
};

Its shape is: a name that is a string, an email that is a string, and an age that is a number. An interface is just a way to write that down as a reusable rule, with a name you can refer to later.

Defining your first interface

You declare an interface with the interface keyword, a name, and a block listing each property and its type. By convention the name starts with a capital letter, like User.

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

Read it out loud: “A User has a name that is a string, an email that is a string, and an age that is a number.” Notice there are no values here, only types. An interface is a contract, not actual data. It describes what a User must look like without being a User itself.

A small but common point of confusion: you can separate the properties with semicolons, commas, or just newlines. All three work. Pick one and stay consistent. Most people use newlines (or semicolons) inside interfaces.

Using an interface

A blueprint is only useful once you attach it to something. The most common place is when you declare a variable or a function parameter. You annotate it with a colon followed by the interface name.

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

const john: User = {
  name: "John Doe",
  email: "john@example.com",
  age: 34,
};

Now TypeScript holds john to the User contract. If you forget a required property, misspell one, or give it the wrong type, you get an error before the code ever runs:

const broken: User = {
  name: "Jane Doe",
  email: "jane@example.com",
  // age is missing -> TypeScript complains here
};

The same blueprint shines in function parameters. Instead of repeating the property list everywhere, you name the interface once and reuse it:

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

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

greet({ name: "John Doe", email: "john@example.com", age: 34 });

Inside greet, TypeScript knows person has a name, an email, and an age. You get autocomplete for them, and a typo like person.naem is caught instantly. That editor help is half the reason interfaces feel so good to use.

Interfaces vanish at runtime

An interface is purely a compile-time tool. After TypeScript compiles your code to JavaScript, every interface disappears. It exists to check your code while you write it, not to do anything when the program runs. So there’s zero performance cost to using them.

Optional properties with ?

Not every property is always present. Maybe most users have a phone number, but some don’t. You don’t want to force a value that isn’t there, and you also don’t want to drop type safety. The answer is the optional marker: a question mark after the property name.

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

The phone? means “a User may have a phone, and if it does, it’s a string.” Both of these objects are now valid:

const withPhone: User = {
  name: "John Doe",
  email: "john@example.com",
  age: 34,
  phone: "+62 812 0000 0000",
};

const withoutPhone: User = {
  name: "Jane Doe",
  email: "jane@example.com",
  age: 29,
};

There’s a catch worth knowing early. When you read an optional property, its type is “the value or undefined,” because it might be missing. So TypeScript will nudge you to check before using it:

function callUser(user: User) {
  // user.phone might be undefined, so guard it first
  if (user.phone) {
    console.log(`Calling ${user.phone}`);
  } else {
    console.log("No phone on file");
  }
}

That little check isn’t busywork. It’s TypeScript reminding you of a real case your code would otherwise forget to handle.

Read-only properties with readonly

Some values should be set once and never change. An id assigned when a record is created is the classic example. You can mark a property as readonly so that any later attempt to reassign it becomes an error.

interface User {
  readonly id: number;
  name: string;
  email: string;
}

const john: User = {
  id: 101,
  name: "John Doe",
  email: "john@example.com",
};

john.name = "John A. Doe"; // fine, name is mutable
john.id = 999;             // error: id is readonly

You can still read a readonly property as much as you like. You just can’t assign to it after the object is created. Think of it as a guardrail: it documents your intent (“this never changes”) and lets the compiler enforce it for you.

readonly is shallow

The readonly keyword protects the property itself, not what’s inside it. If a readonly property holds an array or another object, you can still change that array’s contents. It only blocks reassigning the property to a brand-new value.

Extending interfaces

As an app grows, you’ll often find one shape that’s a more specific version of another. An admin is a user with extra powers. A premium product is a product with a discount field. Rewriting every shared property each time would be tedious and error-prone. Interfaces solve this with extends.

extends lets one interface build on top of another, inheriting all of its properties and adding its own:

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

interface Admin extends User {
  role: "admin";
  canDeleteUsers: boolean;
}

An Admin now requires everything a User requires (name, email) plus its own two properties. You only wrote the shared fields once:

const boss: Admin = {
  name: "Jane Doe",
  email: "jane@example.com",
  role: "admin",
  canDeleteUsers: true,
};

You can extend from more than one interface at a time by separating them with commas, which is handy when a shape combines several smaller ones:

interface Timestamped {
  createdAt: string;
  updatedAt: string;
}

interface Account extends User, Timestamped {
  plan: "free" | "pro";
}

Here, an Account carries the user fields, the timestamp fields, and its own plan. This kind of composition keeps each interface small and focused while letting you assemble bigger shapes from the pieces.

   User                 Timestamped
  ┌──────────┐         ┌────────────┐
  │ name     │         │ createdAt  │
  │ email    │         │ updatedAt  │
  └────┬─────┘         └─────┬──────┘
       │                     │
       └────────┬────────────┘

            Account
       ┌─────────────────┐
       │ name, email     │
       │ createdAt,      │
       │ updatedAt       │
       │ plan            │
       └─────────────────┘

A quick reference for the syntax

Here’s everything in one table so you can scan it later:

Syntax Meaning
name: string Required property of type string
phone?: string Optional property (may be absent)
readonly id: number Can be read, cannot be reassigned
interface B extends A B inherits all properties of A
extends A, C Inherit from several interfaces at once

Interfaces describe objects, not enforce data

An interface checks your code at write time, but it can’t validate data that arrives at runtime, like a response from an API. If a server sends back a field of the wrong type, TypeScript won’t catch it, because it trusted your annotation. For untrusted input you still need real runtime checks.

Recap

Interfaces are how you give a name to the shape of an object and let TypeScript hold your code to it. Here’s the short version of everything above:

  • An interface lists an object’s properties and their types. It’s a compile-time contract that disappears when the code runs.
  • Attach it with a colon (const john: User = ... or function greet(person: User)) to get checking and editor autocomplete.
  • Add ? for optional properties; remember the value might be undefined, so guard before using it.
  • Add readonly for properties that should be set once and never reassigned (the protection is shallow).
  • Use extends to build a more specific interface on top of an existing one, even combining several at once.

Start by giving names to the objects you already pass around in your code. Each interface you write makes the next change safer, because TypeScript now knows what your data is supposed to look like and will speak up the moment something drifts out of shape.

Tags:typescriptinterfacestypesobjectsfrontend
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read