DEV Community

Shanu
Shanu

Posted on • Updated on

TypeScript Generics: How to Write Flexible and Type-Safe Code

TypeScript generics are one of the most powerful tools in a developer's toolkit. They allow you to create flexible, reusable, and type-safe components, making your code robust and maintainable. In this article, we'll explore generics in a hands-on manner, using practical examples to illustrate their power.

The Problem: Building a Versatile Toolset

Imagine you're setting up a workshop, and you need boxes to store different tools like nails, screws, and bolts. Initially, you might create a simple Box class in TypeScript:

class Box {
  content: any;

  constructor(content: any) {
    this.content = content;
  }

  getContent(): any {
    return this.content;
  }
}
Enter fullscreen mode Exit fullscreen mode

This Box class works, but it has a significant flaw: it uses the any type for content. This means you can store anything in the box—nails, screws, or even non-tool items like strings. While this flexibility is convenient, it also introduces risks. There's no type safety, so you could accidentally store a string in a box meant for nails, leading to runtime errors.

Introducing Generics: A Type-Safe Solution

To make our Box class more versatile and type-safe, we can introduce generics. Generics allow us to create a Box that can hold any type of content, but only one type per box.

Here's how you can define a generic Box class:

class Box<T> {
  content: T;

  constructor(content: T) {
    this.content = content;
  }

  getContent(): T {
    return this.content;
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, T is a type variable. It acts as a placeholder that represents the type of the content inside the Box. When you create a Box, you specify what T is (e.g., Nail, Screw, or Bolt), and TypeScript ensures that the box only holds that type.

let nailBox = new Box<Nail>(new Nail());
let screwBox = new Box<Screw>(new Screw());

// Getting the content
let nails = nailBox.getContent(); // TypeScript knows this is a Nail
let screws = screwBox.getContent(); // TypeScript knows this is a Screw
Enter fullscreen mode Exit fullscreen mode

By using generics, you gain type safety. TypeScript will prevent you from accidentally putting a Screw into a Box<Nail> and vice versa. It also provides better autocompletion and error checking, making your code more reliable.

Advanced Generics: Constraining Types

What if you want to ensure that only specific kinds of items can be stored in the Box? For example, only items that have a size property should be allowed. You can achieve this by constraining the type T.

interface Sized {
  size: number;
}

class Box<T extends Sized> {
  content: T;

  constructor(content: T) {
    this.content = content;
  }

  getContentSize(): number {
    return this.content.size;
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, T extends Sized ensures that T must be a type that has a size property. This constraint allows you to use the Box only for items that implement the Sized interface, making the Box more specialized and preventing improper usage.

Real-World Application: Generic Functions

Generics aren't just for classes; they can be used with functions as well. For instance, imagine you want to create a function to swap the contents of two Boxes:

function swap<T>(box1: Box<T>, box2: Box<T>) {
  let temp = box1.content;
  box1.content = box2.content;
  box2.content = temp;
}

let box1 = new Box<Nail>(new Nail());
let box2 = new Box<Nail>(new Nail());

swap(box1, box2);
Enter fullscreen mode Exit fullscreen mode

Here, the swap function uses T to ensure that both Boxes hold the same type of content. It wouldn't make sense to swap contents of different types (like a Box<Nail> with a Box<Screw>), so TypeScript enforces that the types match.

The Power of Generics

Generics provide a way to create flexible, reusable, and type-safe code. They allow you to write functions, classes, and interfaces that can work with any type while still enforcing specific constraints when needed. Whether you're building libraries, frameworks, or simply organizing code in a large project, generics are an essential tool in TypeScript that can significantly improve your code quality.

Conclusion

Generics are more than just a way to handle multiple types—they're a method for building versatile and reliable software. By embracing generics in your TypeScript projects, you can ensure that your code is not only flexible but also robust and maintainable. Whether you're developing for the frontend, backend, or full stack, mastering generics will elevate your TypeScript skills to the next level.

Top comments (2)

Collapse
 
wizard798 profile image
Wizard

Wow, great article, gonna use this from now on, I used to think that currently I'm user and I won't insert diff data so used Interfaces or Union types, but Generics are straight forward, Thanks for help

Collapse
 
shanu001x profile image
Shanu

Thank you so much! I'm glad you found the article helpful.