Interfaces: A TypeScript construct for defining the shape of an object, specifying properties, their types, and optionally methods.
interface InterfaceName { property: type; method?(): returnType;
}
// Defining interfaces
interface User { id: number; name: string; email: string; greet(): string;
} // Using interface
const user: User = { id: 1, name: "kristal", email: "[email protected]", greet() { return `Hello, ${this.name}!`; }
}; console.log(user.greet());
Output:Hello, kristal!
Optional Properties: Properties that may or may not be present, denoted by ?.
property?: type.Readonly Properties: Properties that can only be set during initialization, denoted by readonly.
readonly property: type.
// Optional and readonly properties
interface Product { readonly id: number; name: string; description?: string; // Optional price: number;
} const product: Product = { id: 101, name: "Laptop", price: 999.99 // description omitted (optional)
}; console.log(product);
// product.id = 102; // Error: Cannot assign to 'id' because it is a read-only property
product.price = 1099.99; // Allowed
product.description = "High-performance laptop"; // Allowed
console.log(product);
Output:
{ id: 101, name: 'Laptop', price: 999.99 }
{ id: 101, name: 'Laptop', price: 1099.99, description: 'High-performance laptop' }
Type Aliases: Define a type with a name, using the type keyword.
type TypeName = typeDefinition.Interfaces: Specifically define object shapes, using the interface keyword.
Key Differences:
| Feature | Interface | Type Alias |
|---|---|---|
| Declaration | interface Name { ... } | type Name = { ... } |
| Extending | Use extends for inheritance | Use & for intersection |
| Merging | Supports declaration merging | No merging; redefinition causes error |
| Type Flexibility | Primarily for object shapes | Any type (unions, primitives, etc.) |
| Use Case | Object-oriented design, reusable contracts | Flexible types, including non-objects |
| Implement in Classes | Can be implemented (implements) | Cannot be directly implemented |
Use Case:
// Interface vs Type Alias
// Interface
interface User { id: number; name: string;
} // Interface merging
interface User { email: string;
} // Type Alias
type Person = { id: number; name: string;
} & { age: number }; // Intersection // Type Alias for union types
type ID = number | string; // Using interface
const user: User = { id: 1, name: "kristal", email: "[email protected]"
}; // Using type alias
const person: Person = { id: 2, name: "Sashi", age: 30
}; const id: ID = "ID123"; // Can be number or string console.log(user);
console.log(person);
console.log(id); // Class implementing interface
class Employee implements User { id: number; name: string; email: string; constructor(id: number, name: string, email: string) { this.id = id; this.name = name; this.email = email; }
} // Type alias cannot be implemented directly
// class Invalid implements Person {} // Error
Output:
{ id: 1, name: 'kristal', email: '[email protected]' }
{ id: 2, name: 'Sashi', age: 30 }
ID123
Project Structure:
ts-interfaces-types/
├── src/
│ └── main.ts
├── tsconfig.json
└── package.json
main.ts:
// Comprehensive example
// Interface with optional and readonly properties
interface Person { readonly id: number; name: string; email?: string; // Optional greet(): string;
} // Extending interface
interface Employee extends Person { role: string; department?: string; // Optional
} // Type alias for union
type Role = "Developer" | "Manager" | "Admin"; // Type alias with intersection
type Manager = Employee & { teamSize: number;
}; // Class implementing interface
class Staff implements Employee { readonly id: number; name: string; email?: string; role: Role; department?: string; constructor(id: number, name: string, role: Role, email?: string, department?: string) { this.id = id; this.name = name; this.role = role; this.email = email; this.department = department; } greet(): string { return `Hello, ${this.name} (${this.role})`; }
} // Using type alias and interface
const manager: Manager = { id: 1, name: "kristal", role: "Manager", email: "[email protected]", department: "Engineering", teamSize: 10, greet() { return `Hello, ${this.name}, managing ${this.teamSize} people`; }
}; const staff: Employee = new Staff(2, "Sashi", "Developer", "[email protected]"); // Testing
console.log(manager.greet());
console.log(staff.greet());
// manager.id = 3; // Error: Cannot assign to 'id' because it is a read-only property
console.log(manager);
console.log(staff);
package.json:
{ "name": "ts-interfaces-types", "version": "1.0.0", "scripts": { "start": "tsc && node dist/main.js", "build": "tsc", "watch": "tsc --watch" }, "devDependencies": { "typescript": "^5.6.2" }
}
tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["node_modules"]
}
Steps to Run:
npm install.npm run build.npm start.Output:
Hello, kristal, managing 10 people
Hello, Sashi (Developer)
{ id: 1, name: 'kristal', role: 'Manager', email: '[email protected]', department: 'Engineering', teamSize: 10, greet: [Function: greet] }
Staff { id: 2, name: 'Sashi', role: 'Developer', email: '[email protected]', department: undefined }
Description:
Person and Employee define object shapes with required and optional properties.email and department are optional; id is readonly.Employee extends Person for inheritance.Role uses a union type; Manager uses an intersection.Staff implements Employee, showing interface-class compatibility.readonly, expecting runtime enforcement (TypeScript is compile-time).User vs. IUser).readonly for immutable fields like IDs or constants.?) for fields that may be absent.tsc to catch errors early.tslint or eslint with TypeScript plugins for consistency.