Optional properties in interfaces are marked with a '?' symbol after the property name. They don't need to be implemented when using the interface. Example: interface User { name: string; email?: string; } This means email is optional and can be undefined.
Index signatures allow interfaces to describe objects with dynamic property names. Example: interface StringMap { [key: string]: string; } This means the object can have any number of string properties, where both the key and value are strings.
Function types in interfaces can be defined using call signatures. Example: interface Calculation { (x: number, y: number): number; } This defines an interface for a function that takes two numbers and returns a number. You can also use method syntax: interface Math { add(x: number, y: number): number; }
Mapped types can be used to transform interface properties systematically. Example: type Readonly<T> = { readonly [P in keyof T]: T[P] }; interface User { name: string; age: number; } type ReadonlyUser = Readonly<User>;
Utility types provide common type transformations for interfaces. Example: interface User { name: string; age?: number; } type RequiredUser = Required<User>; type PartialUser = Partial<User>; These transform optional properties to required and vice versa.
Type guards help narrow down interface types in conditional blocks. Example: interface Bird { fly(): void; } interface Fish { swim(): void; } function isBird(pet: Bird | Fish): pet is Bird { return (pet as Bird).fly !== undefined; }
Interfaces can describe array-like structures using index signatures or extending Array. Example: interface StringArray { [index: number]: string; length: number; } interface MyArray<T> extends Array<T> { getFirst(): T; }
Key differences include: 1) Interfaces are extendable through declaration merging while type aliases are not, 2) Interfaces can only describe object shapes while type aliases can create unions, primitives, and tuples, 3) Interfaces are better for defining contracts in object-oriented programming while type aliases are preferable for functional programming patterns. Example: interface vs type Point = { x: number; y: number; }
Hybrid types combine function types with object properties. Example: interface Counter { (start: number): string; interval: number; reset(): void; } This creates an interface that can be both called as a function and has properties/methods.
An interface can extend multiple interfaces using comma-separated names. Example: interface Shape { color: string; } interface Sizeable { size: number; } interface Circle extends Shape, Sizeable { radius: number; } This combines all properties from the extended interfaces.
Literal types specify exact values that a property must have. Example: interface Config { mode: 'development' | 'production'; port: 80 | 443; } This ensures properties can only have specific values.
Optional methods in interfaces are marked with '?' and don't need to be implemented. Example: interface Logger { log(message: string): void; error?(error: Error): void; } This makes the error method optional.
Readonly properties are marked with the 'readonly' modifier and cannot be changed after initialization. Example: interface Point { readonly x: number; readonly y: number; } This ensures immutability of these properties after they are set initially.
Interfaces can use generic type parameters to create flexible, reusable definitions. Example: interface Container<T> { value: T; getValue(): T; } This allows the interface to work with any type while maintaining type safety.
Recursive types are interfaces that reference themselves in their definition. Example: interface TreeNode { value: string; children?: TreeNode[]; } This creates a tree-like structure where each node can have child nodes of the same type.
Method overloading in interfaces is achieved by declaring multiple function signatures. Example: interface Document { createElement(tagName: any): any; createElement(tagName: 'div'): HTMLDivElement; createElement(tagName: 'span'): HTMLSpanElement; }
Union types (|) allow a value to be one of several types, while intersection types (&) combine multiple types. Example: interface Bird { fly(): void; } interface Fish { swim(): void; } type Pet = Bird | Fish; type Amphibian = Bird & Fish;
Constructor signatures define how a class constructor should look. Example: interface ClockConstructor { new (hour: number, minute: number): ClockInterface; } This ensures classes implementing the interface have the specified constructor.
Interfaces can define multiple call signatures for function overloading. Example: interface StringNumberFunction { (str: string): number; (str: string, radix: number): number; } This allows functions to be called with different parameter combinations.
Interfaces can define static members that must be implemented by the class itself rather than instances. Example: interface ClockConstructor { new (): ClockInterface; currentTime: Date; } The currentTime property must be implemented as a static property.
Interfaces can inherit from other interfaces using the 'extends' keyword. They can extend multiple interfaces, creating a combination of all properties. Example: interface Shape { color: string; } interface Circle extends Shape { radius: number; } A class implementing Circle must provide both color and radius properties.
Declaration merging allows multiple interface declarations with the same name to be automatically combined. Example: interface User { name: string; } interface User { age: number; } Results in an interface requiring both name and age. This is unique to interfaces and not available with type aliases.
Computed properties allow property names to be expressions. Example: type Events = 'click' | 'focus'; interface Handlers { [K in Events]: (event: any) => void; } This creates properties for each event type automatically.
Classes can implement interfaces using the 'implements' keyword. Example: interface Printable { print(): void; } class Document implements Printable { print() { console.log('printing...'); } } The class must provide implementations for all interface members.
Interfaces can be used as constraints for generic types. Example: interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): number { return arg.length; } This ensures the generic type has a length property.