Isaac.

typescript

Type Safety in TypeScript

Leverage TypeScript's type system for safer, more maintainable code.

By Emem IsaacNovember 7, 20243 min read
#typescript#types#type safety#generics#interfaces
Share:

A Simple Analogy

Types in TypeScript are like contract agreements. They specify what's promised and catch violations at compile-time.


Why Type Safety?

  • Fewer bugs: Errors at compile-time
  • Better IDE support: Autocomplete and suggestions
  • Documentation: Types document intent
  • Refactoring: Safe code changes
  • Maintainability: Easier to understand

Basic Types

// Primitives
const name: string = "Alice";
const age: number = 30;
const active: boolean = true;
const nothing: null = null;
const unknown: undefined = undefined;

// Collections
const numbers: number[] = [1, 2, 3];
const mixed: (string | number)[] = [1, "two"];
const tuple: [string, number] = ["age", 30];

// Objects
interface User {
  id: number;
  name: string;
  email?: string;  // Optional
  readonly createdAt: Date;  // Readonly
}

const user: User = {
  id: 1,
  name: "Bob",
  createdAt: new Date()
};

Generics

// Generic function
function getFirst<T>(items: T[]): T {
  return items[0];
}

const firstNumber = getFirst([1, 2, 3]);  // number
const firstString = getFirst(["a", "b"]);  // string

// Generic interface
interface Container<T> {
  value: T;
  getValue(): T;
}

const numberContainer: Container<number> = {
  value: 42,
  getValue() { return this.value; }
};

// Generic constraints
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ a: 1 }, { b: 2 });  // { a: 1; b: 2 }

Union and Intersection Types

// Union: one of multiple types
type Status = "pending" | "completed" | "failed";
type ID = string | number;

function processStatus(status: Status) {
  switch (status) {
    case "pending": return "Waiting...";
    case "completed": return "Done!";
    case "failed": return "Error";
  }
}

// Intersection: combination of types
interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Named {
  name: string;
}

type Document = Timestamped & Named;

const doc: Document = {
  name: "Report",
  createdAt: new Date(),
  updatedAt: new Date()
};

Utility Types

// Partial: all properties optional
type PartialUser = Partial<User>;

// Required: all properties required
type RequiredUser = Required<User>;

// Readonly: all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick: select specific properties
type UserPreview = Pick<User, "id" | "name">;

// Omit: exclude specific properties
type UserWithoutEmail = Omit<User, "email">;

// Record: object with specific keys
type Role = "admin" | "user" | "guest";
const permissions: Record<Role, boolean> = {
  admin: true,
  user: true,
  guest: false
};

// Exclude: remove types from union
type NonString = Exclude<string | number | boolean, string>;  // number | boolean

Type Guards

// Type predicate
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === "object" &&
    obj !== null &&
    "id" in obj &&
    "name" in obj &&
    typeof obj.id === "number" &&
    typeof obj.name === "string"
  );
}

// Usage
const data: unknown = JSON.parse(jsonString);
if (isUser(data)) {
  console.log(data.name);  // Safe: TypeScript knows it's User
}

// instanceof guard
if (error instanceof RangeError) {
  console.log("Range error occurred");
}

// Discriminated unions
type Result = 
  | { status: "success"; data: string }
  | { status: "error"; error: Error };

function handleResult(result: Result) {
  if (result.status === "success") {
    console.log(result.data);
  } else {
    console.log(result.error);
  }
}

Best Practices

  1. Avoid any: Use specific types
  2. Enable strict mode: Catch more errors
  3. Use type guards: Safe type narrowing
  4. Document with types: Types are documentation
  5. Leverage inference: Let TypeScript infer when possible

Related Concepts

  • Advanced generics
  • Conditional types
  • Mapped types
  • Type inference

Summary

TypeScript's type system prevents entire categories of bugs at compile-time. Use interfaces, generics, and utility types to build robust applications.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours