TypeScript: Types, Interfaces and Enums
From primitive types to powerful enums, this post shows how TypeScript’s type system helps you catch bugs early and design cleaner code.


Jhonatas Matos
Understanding types, interfaces and enums is essencial for writing type-safe and maintainable code. In this post, I'm going to explore these core concepts with practical examples.
Why TypeScript?
Because it’s a superset of JavaScript that adds static types, making it easier to catch errors early and improve code quality. In this post, I’ll cover the basics of TypeScript types, interfaces and enums, which are essential for writing robust applications.
✔ Catches errors during development (not at runtime)
✔ Makes code more predictable and self-documenting
✔ Improves IDE autocompletion and tooling
Types
Types in TypeScript allow you to define the shape of data. They can be primitive types like string
, number
, boolean
, or more complex types like arrays, tuples, objects, and more.
Here’s a quick and dynamic overview with practical examples:
Primitive Types
Primitives types in TypeScript include string
, number
, boolean
are fundamentals blocks of TypeScript.
let name: string = "Ana";
let age: number = 25;
let isActive: boolean = true;
Composite Types
Composite types involve more complex types, such as arrays and objects:
// Array of strings
let hobbies: string[] = ["Reading", "Sports"];
// Object with specific types
let user: { name: string; age: number } = {
name: "Ana",
age: 25,
};
Special Types
TypeScript also provides special types like Union, Intersection, and Tuple:
// Union: Multiple allowed types
let id: string | number = "ABC123";
// Tuple: Fixed-length array
let coordinates: [number, number] = [10.5, -72.3];
Type vs Interface
Scenario | Use Type | Use Interface |
---|---|---|
Define object shapes | ✅ Yes | ✅ (Best choice) |
Union/Intersection types | ✅ (Ex: string | number ) | ❌ Not supported |
Extending/Inheriting | ❌ (Limited) | ✅ (With extends ) |
Implementing in classes | ❌ | ✅ (With implements ) |
Example:
// Using type (union)
type ID = string | number;
// Using interface (extensible)
interface User {
id: ID;
name: string;
}
Interfaces
Interfaces in TypeScript are used to define the structure of an object. They allow you to specify the properties and methods that an object must have.
Basic Interface
Basic interface definition with properties and methods.
interface User {
name: string;
age: number;
isAdmin: boolean;
}
let user: User = {
name: "Jhonatas",
age: 30,
isAdmin: true,
};
Optional Properties
Add ?
for properties that might not exist.
interface Config {
apiUrl: string;
timeout?: number; // Optional
}
const devConfig: Config = {
apiUrl: "http://localhost:3000",
// `timeout` can be omitted
};
Readonly Properties
Use readonly
to prevent modification of properties after initialization.
interface AppConstants {
readonly version: string;
}
const constants: AppConstants = {
version: "1.0.0"
};
// constants.version = "2.0.0"; 🚨 Error!
// Use for configuration or environment values.
Extending Interfaces
Interfaces can extend other interfaces, allowing you to create more complex structures by building on existing ones.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const myDog: Dog = {
name: "Rex",
breed: "Labrador"
};
Interfaces define object contracts.
Use?
for optional fields, readonly for immutability.
Extend with extends for reusable designs.
Interfaces vs. Types
Prefer interfaces for objects, types for unions/aliases.
In general, you should use interfaces when you want to define the shape of an object, especially when you expect that shape to be implemented by multiple classes or objects. Use types when you want to create a more complex type by combining existing types or when you need to use union or intersection types.
Example:
// Interface (better for objects)
interface Point {
x: number;
y: number;
}
// Type (better for unions)
type ID = string | number;
Enums
Enums are a special type in TypeScript that allows you to define a set of named constants. They are useful when you have a fixed set of related values. Here’s an example:
String enums
enum UserRole {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST"
}
function greetUser(role: UserRole) {
if (role === UserRole.Admin) {
return "Welcome, Administrator!";
}
return "Hello, user!";
}
console.log(greetUser(UserRole.Admin)); // "Welcome, Administrator!"
Numeric Enums
enum StatusCode {
Success = 200, // Explicit value
NotFound = 404,
ServerError = 500
}
function handleResponse(code: StatusCode) {
console.log(`Received status: ${code}`);
}
handleResponse(StatusCode.Success); // "Received status: 200"
Const Enums
const enum Direction {
Up = "UP",
Down = "DOWN"
}
const move = Direction.Up; // Compiled to: "UP" (no runtime enum)
Real-World Example
enum OrderStatus {
Draft = "DRAFT",
Paid = "PAID",
Shipped = "SHIPPED"
}
interface Order {
id: string;
status: OrderStatus;
}
const currentOrder: Order = {
id: "ord_123",
status: OrderStatus.Paid
};
Prefer string enums for clarity Avoid numeric enums unless interacting with legacy code Use const enum for compile-time optimization
Read more
TypeScript Handbook
Type vs Interface (Article)
Playground TypeScript (Test examples directly in the browser)
Practical Exercise
Link to GitHub project
Conclusion
In this post, we covered the basics of TypeScript types, interfaces, and enums. By leveraging these features, you can create more robust and maintainable applications. TypeScript’s static typing helps catch errors early, while interfaces and enums provide a clear structure to your code. Start using TypeScript today and experience the benefits of type safety in your JavaScript projects!