Interfaces describe the shape of objects by declaring the names and types of their members. A simple interface Person { name: string; age: number; } tells the compiler that any value typed as Person must have those two properties with those exact types. Because interfaces exist only at compile time, they impose type-checking guarantees without any runtime overhead.
TypeScript also offers type aliases through the type keyword, which can describe unions, intersections, primitives, and other compound types. The choice between interface and type often comes down to intent: interfaces are designed for object shapes and uniquely support declaration merging, while type aliases are more flexible for unions, intersections, and computed utilities. Many developers reach for interfaces when modeling object-oriented contracts and for type aliases when assembling complex type-level logic.
Interfaces and types can be combined or extended in several ways. Index signatures allow dynamic key names for objects whose keys are not known ahead of time, such as interface StringDict { [key: string]: string; }. The readonly modifier marks a property as assignable only at initialization, which is ideal for immutable data. Interfaces inherit members from other interfaces using extends, and unions allow a value to be one of several types with string | number, while intersections require a value to satisfy every type at once with User & { permissions: string[]; }. When the compiler needs a hint about a value's type, type assertions using the as keyword tell TypeScript to treat a value as a specific type, although this should be done cautiously because it bypasses the usual checks.