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.

TypeScript: Types, Interfaces and Enums
Jhonatas Matos

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

ScenarioUse TypeUse 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;
}

Link to types documentation

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 projectGitHub Octocat

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!