Skip to content

TypeScript - Interfaces

An interface in TypeScript acts as a contract that defines the shape of an object. It specifies the names and types of properties and methods that an object must have to be considered compatible with that interface. Interfaces are a core feature of TypeScript’s structural type system (often called “duck typing”).

Interfaces only contain the declaration of members (properties, method signatures); they do not provide implementation details. It’s the responsibility of the object or class that uses the interface to adhere to its structure.

Interfaces are purely a compile-time construct. They are not compiled into JavaScript and have zero runtime overhead. Their purpose is solely for static type checking during development.

Use the interface keyword followed by the interface name (typically starting with an uppercase ‘I’ by convention, though not strictly required, e.g., IPerson or Person).

interface InterfaceName {
propertyName: type;
optionalProperty?: type; // Optional properties use ?
readonly readOnlyProperty: type; // Readonly properties cannot be changed after initialization
methodName(param: type): returnType;
optionalMethod?(param: type): returnType;
}
interface Person {
firstName: string;
lastName: string;
age?: number; // Optional property
readonly id: number;
sayHi: () => string; // Method signature
}
// Object conforming to the Person interface
const customer: Person = {
firstName: "Tom",
lastName: "Hanks",
id: 101,
sayHi: (): string => {
return "Hi there";
} // age is optional, so it's okay to omit
};
// Another object conforming to the interface
const employee: Person = {
firstName: "Jim",
lastName: "Blakes",
age: 35,
id: 205,
sayHi: () => "Hello!!!"
};
console.log("Customer Object");
console.log(customer.firstName); // Output: Tom
console.log(customer.lastName); // Output: Hanks
console.log(customer.sayHi()); // Output: Hi there
// customer.id = 102; // Error: Cannot assign to 'id' because it is a read-only property.
console.log("\nEmployee Object");
console.log(employee.firstName); // Output: Jim
console.log(employee.age); // Output: 35

TypeScript checks if an object has at least the properties defined by the interface with the correct types. Extra properties are generally allowed unless specifically checked (e.g., when assigning object literals directly).

Interfaces can also describe the signature of a function.

interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
// Using arrow function syntax
const myOtherSearch: SearchFunc = (source, substring) => {
return source.includes(substring);
};
console.log(mySearch("hello world", "world")); // Output: true
console.log(myOtherSearch("typescript", "java")); // Output: false

Interfaces for Array Types (Index Signatures)

Section titled “Interfaces for Array Types (Index Signatures)”

Interfaces can define the type of index used to access array-like structures and the type of values stored.

// Describes an array-like object where keys are numbers and values are strings
interface StringArray {
[index: number]: string;
}
let myStrArray: StringArray;
myStrArray = ["Bob", "Alice"];
console.log(myStrArray[0]); // Output: Bob
// myStrArray[2] = 123; // Error: Type 'number' is not assignable to type 'string'.
// Describes an object where keys are strings and values are numbers
interface NumberDictionary {
[index: string]: number;
length: number; // Can have other specific properties too
// name: string; // Error: Property 'name' of type 'string' is not assignable to string index type 'number'.
}
let ageMap: NumberDictionary = {
"Alice": 30,
"Bob": 35,
length: 2
};
console.log(ageMap["Alice"]); // Output: 30
console.log(ageMap.length); // Output: 2
// ageMap["Charlie"] = "forty"; // Error: Type 'string' is not assignable to type 'number'.

Note: If an interface has both a string and a number index signature, the type returned by the numeric indexer must be a subtype of the type returned by the string indexer. Also, properties with specific names must conform to the index signature type.

Interfaces can inherit from one or more other interfaces using the extends keyword. This allows combining multiple interface definitions.

interface ChildInterface extends ParentInterface { ... }
interface ChildInterface extends Parent1, Parent2, ... { ... }
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
// Square extends Shape and PenStroke
interface Square extends Shape, PenStroke {
sideLength: number;
}
let mySquare: Square = {
color: "blue",
penWidth: 5.0,
sideLength: 10
};
console.log(`Square Color: ${mySquare.color}`);
console.log(`Square Pen Width: ${mySquare.penWidth}`);
console.log(`Square Side Length: ${mySquare.sideLength}`);

TypeScript also has type aliases (type), which can often achieve similar results to interfaces, especially for defining object shapes.

type Point = {
x: number;
y: number;
};
interface IPoint {
x: number;
y: number;
}

Key Differences:

  • Extensibility: Interfaces can be declared multiple times and will be merged (Declaration Merging). Interfaces can also be extended using extends. Type aliases cannot be merged after creation, although intersections (&) can combine types.
  • Implementation: Classes can implement interfaces to ensure they adhere to the contract. Classes cannot directly implement type aliases that define object shapes (though they can implement types created from type aliases via intersections, etc.).
  • Flexibility: Type aliases are slightly more flexible; they can represent not just object shapes but also union types, intersection types, tuples, mapped types, and primitive type aliases (e.g., type UserID = string;).

Use interfaces primarily for defining object shapes or contracts that classes should implement. Use type aliases for more general type definitions, including unions, intersections, tuples, or when you need features not available with interfaces.