TypeScript Interfaces
TypeScript interfaces are a powerful way to define the structure of objects in your code. They help you create clear contracts about what shape your data should have, making your code more predictable and easier to understand.
An interface is like a blueprint that describes what properties and methods an object should have:
Basic Interface interface User {
id : number ;
name : string ;
email : string ;
}
// Creating an object that matches the interface
const john : User = {
id : 1 ,
name : "John Doe" ,
email : "john@example.com" ,
};
CopyCopied!
Not all properties need to be required. Use the ? symbol to mark properties as optional:
Optional properties interface Product {
id : number ;
name : string ;
price : number ;
description ?: string ; // Optional property
}
// Both of these are valid
const laptop : Product = {
id : 1 ,
name : "Laptop" ,
price : 999 ,
};
const phone : Product = {
id : 2 ,
name : "Phone" ,
price : 699 ,
description : "Latest smartphone model" ,
};
CopyCopied!
If you want to prevent properties from being changed after initialization, use the readonly modifier:
ReadOnly interface Point {
readonly x : number ;
readonly y : number ;
}
const point : Point = { x : 10 , y : 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property
CopyCopied!
Interfaces can also describe functions:
Function description interface CalculateTotal {
(price : number , quantity : number , taxRate : number ) : number ;
}
const calculateTotal : CalculateTotal = (price , quantity , taxRate) => {
return price * quantity * ( 1 + taxRate);
};
CopyCopied!
You can define methods inside interfaces:
Model definition interface Logger {
log (message : string ) : void ;
error (message : string ) : void ;
}
const consoleLogger : Logger = {
log (message) {
console .log ( `LOG: ${ message } ` );
} ,
error (message) {
console .error ( `ERROR: ${ message } ` );
} ,
};
CopyCopied!
Interfaces can extend other interfaces to inherit their properties:
Extending interface interface Person {
name : string ;
age : number ;
}
interface Employee extends Person {
employeeId : number ;
department : string ;
}
const employee : Employee = {
name : "Alice" ,
age : 30 ,
employeeId : 123 ,
department : "Engineering" ,
};
CopyCopied!
TypeScript offers two ways to define object types: interface
and type
. While they're similar in many ways, understanding their differences helps you choose the right one for your needs.
Basic Syntax Comparison
// Interface
interface User {
name : string ;
age : number ;
}
// Type
type User = {
name : string ;
age : number ;
};
CopyCopied!
Key Differences
1. Declaration Merging
Interfaces can be defined multiple times, and they will be merged:
// Interface merging
interface User {
name : string ;
}
interface User {
age : number ;
}
// The User interface now has both name and age
const user : User = {
name : "John" ,
age : 30 ,
};
CopyCopied!
Types cannot be redefined or merged:
type User = {
name : string ;
};
// Error: Duplicate identifier 'User'
// type User = {
// age: number;
// };
CopyCopied!
2. Extending
Interfaces use the extends
keyword:
interface Animal {
name : string ;
}
interface Dog extends Animal {
breed : string ;
}
CopyCopied!
Types use the &
operator (intersection):
type Animal = {
name : string ;
};
type Dog = Animal & {
breed : string ;
};
CopyCopied!
3. Additional Capabilities of Types
Types can do things that interfaces cannot:
// Union types
type ID = string | number ;
// Literal types
type Direction = "north" | "south" | "east" | "west" ;
// Type aliases for primitives
type Age = number ;
// Mapped types
type ReadonlyUser = {
readonly [ K in keyof User ] : User [ K ];
};
CopyCopied!
Overview: Interfaces vs Types
Type vs Interface Comparison Feature Interfaces Types Primary Use Object shapes, API contracts Unions, intersections, primitives Declaration Merging Yes No Extends/Implements via extends
via &
(intersection)
Primitive Types No Yes Union Types No Yes Tuple Types No Yes Can be Computed No Yes
Use Interfaces When:
You're defining objects or classes that implement a contract:
interface Repository {
findById (id : string ) : unknown ;
save (data : unknown ) : void ;
}
class UserRepository implements Repository {
// Implementation here
}
CopyCopied!
You want to allow declaration merging:
// Useful when extending libraries or working with declaration files
interface Window {
customProperty : string ;
}
CopyCopied!
You're working with object shapes for data:
interface User {
id : number ;
name : string ;
}
CopyCopied!
Use Types When:
You need unions, intersections, or tuples:
type Result = Success | Error ;
type Coordinates = [ number , number ];
CopyCopied!
You want to use primitives or literals:
type ID = string ;
type Status = "pending" | "completed" | "failed" ;
CopyCopied!
You need to manipulate types:
type UserKeys = keyof User ;
type PartialUser = Partial < User >;
CopyCopied!
React Component Props
// Props interface for a Button component
interface ButtonProps {
text : string ;
onClick : () => void ;
color ?: "primary" | "secondary" | "danger" ;
disabled ?: boolean ;
}
function Button ({ text , onClick , color = "primary" , disabled = false } : ButtonProps ) {
return (
< button
onClick = {onClick}
disabled = {disabled}
className = { `btn btn- ${ color } ` }
>
{text}
</ button >
);
}
// Usage
< Button
text = "Click me"
onClick = {() => alert ( "Clicked!" )}
color = "primary"
/>
CopyCopied!
Form Data Interface
interface FormData {
username : string ;
password : string ;
rememberMe ?: boolean ;
}
function submitForm (data : FormData ) {
console .log ( "Submitting:" , data);
// API call to submit the form
}
const loginData : FormData = {
username : "john_doe" ,
password : "secure_password" ,
rememberMe : true ,
};
submitForm (loginData);
CopyCopied!
1. Use PascalCase for naming interfaces:
interface UserProfile { ... } // Good
interface userProfile { ... } // Not ideal
CopyCopied!
2. Don't use the "I" prefix:
interface User { ... } // Good
interface IUser { ... } // Not recommended
CopyCopied!
3. Keep interfaces focused on a single concept:
// Better to have two separate interfaces
interface User { ... }
interface UserPreferences { ... }
// Instead of one large interface
// interface UserWithPreferences { ... }
CopyCopied!
4. Use readonly for immutable properties:
interface Config {
readonly apiKey : string ;
readonly serverUrl : string ;
}
CopyCopied!
5. Document your interfaces with JSDoc:
/**
* Represents a user in the system
*/
interface User {
/** Unique identifier */
id : number ;
/** User's full name */
name : string ;
}
CopyCopied!
Exercise 1: Basic Interface
Create an interface for a Book
with these properties:
title
(string)
author
(string)
pages
(number)
isPublished
(boolean)
genres
(array of strings)
Then create a few book objects using this interface.
Exercise 2: Interface with Optional Properties
Create an interface for a UserProfile
with these properties:
id
(number)
username
(string)
email
(string)
bio
(string, optional)
age
(number, optional)
isPremium
(boolean, optional)
Create a function that prints user information, with special handling for optional properties.
Exercise 3: Interfaces with Methods
Create an interface for a Calculator
with these methods:
add(a: number, b: number): number
subtract(a: number, b: number): number
multiply(a: number, b: number): number
divide(a: number, b: number): number
Then implement this interface with a class.