Skip to content
TypeScript ts interfaces 4 min read

Interface Basics

An interface is TypeScript’s primary tool for naming the shape of an object: the set of properties and methods a value is expected to have. Interfaces exist purely at compile time — they emit no JavaScript — and they describe contracts that the compiler enforces structurally. Once you declare an interface, you can reuse it anywhere a type annotation is allowed, and editors will surface its members through autocomplete. This page covers how to declare interfaces, type properties and methods, and how TypeScript’s structural typing decides whether a value satisfies an interface.

Declaring an interface

An interface lists named members and their types between braces. By convention interface names use PascalCase. Once declared, the interface becomes a reusable type you can apply with a regular annotation.

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

const alice: User = {
  id: 1,
  name: "Alice",
  email: "[email protected]",
};

Each member is required by default, so the object literal must provide every property with a compatible type. Omitting email, or assigning a string to id, is rejected at compile time. Note that members are separated by semicolons or newlines, not commas, though commas are also tolerated.

// ❌ Error: Property 'email' is missing in type '{ id: number; name: string; }'
const bob: User = { id: 2, name: "Bob" };

Typing methods and function members

Interfaces describe behavior as well as data. You can declare a method using either method-shorthand syntax or a property whose type is a function. Both are valid; method shorthand reads more naturally for object APIs.

interface Logger {
  // method shorthand
  log(message: string): void;
  // property with a function type
  format: (level: string, message: string) => string;
}

const consoleLogger: Logger = {
  log(message) {
    console.log(message);
  },
  format: (level, message) => `[${level}] ${message}`,
};

The parameter and return types defined in the interface flow into the implementation, so inside log the message parameter is already known to be a string without re-annotating it. This is contextual typing driven by the interface.

Structural typing

TypeScript uses structural typing: a value is assignable to an interface if it has at least the required members with compatible types, regardless of whether it was explicitly declared as that interface. This is sometimes called “duck typing”.

interface Point {
  x: number;
  y: number;
}

function distanceFromOrigin(p: Point): number {
  return Math.sqrt(p.x ** 2 + p.y ** 2);
}

const vector = { x: 3, y: 4, z: 5 };
distanceFromOrigin(vector); // ✅ ok — vector has x and y

Because vector has both x and y (the extra z is harmless when passed through a variable), it satisfies Point. The same object shape works anywhere Point is expected even though vector was never typed as Point.

Object literals passed directly as arguments are subject to excess property checks, which flag unknown properties like z. Assign the literal to a variable first, or use a known property, to bypass that stricter check.

Interfaces describe shapes, not classes

An interface can type any object, including class instances, plain literals, and function results. A class can also promise to satisfy an interface using implements, which checks the class against the contract without affecting its runtime behavior.

interface Comparable<T> {
  compareTo(other: T): number;
}

class Version implements Comparable<Version> {
  constructor(public readonly value: number) {}

  compareTo(other: Version): number {
    return this.value - other.value;
  }
}

Here Version must provide a compareTo method matching the interface or the compiler errors. The interface itself produces no code; it only constrains the class declaration.

Interface vs inline object type

You do not always need a named interface — an inline object type literal works for one-off shapes. Reach for an interface when a shape is reused, needs a clear name in error messages, or will be extended.

ApproachBest forReusableAppears by name in errors
interface User { ... }Shared, named contractsYesYes
Inline { id: number }One-off local annotationsNoExpanded inline
type User = { ... }Shapes plus unions/mapped typesYesYes

Best Practices

  • Name interfaces in PascalCase and after the concept they model, not with a I prefix.
  • Keep each member’s type as precise as possible; avoid any inside interfaces.
  • Use method shorthand for object APIs and function-typed properties for callbacks.
  • Prefer a named interface over a repeated inline object type for shared shapes.
  • Let structural typing work for you, but remember excess property checks on direct literals.
  • Use implements to verify classes against an interface without coupling runtime code.
  • Split large interfaces into smaller, composable ones that you can extend.
Last updated June 29, 2026
Was this helpful?