Understanding TypeScript Generics: From Basics to Advanced Patterns 🚀

Understanding TypeScript Generics: From Basics to Advanced Patterns 🚀

Ihor (Harry) Chyshkala
Ihor (Harry) ChyshkalaAuthor
|2 min read

Audio Narration

Understanding TypeScript Generics: From Basics to Advanced Patterns 🚀

0:000:00
AI-generated audio narration

Hey TypeScript enthusiasts! 👋 Ready to level up your type-safety game? Today, we're diving deep into one of TypeScript's most powerful features - Generics. Don't worry if they seem intimidating at first; by the end of this article, you'll be wielding them like a pro!

What Are Generics? 🤔

Think of Generics as type-level functions - they let you write code that works with multiple types while maintaining full type safety. Instead of hardcoding specific types, you can create flexible, reusable components that work with any type you throw at them.

Your First Generic Function 🎯

Let's start with something simple - a function that returns whatever you pass to it:

typescript
1function echo<T>(value: T): T {
2    return value;
3}
4
5// TypeScript knows exactly what types these are!
6const str = echo("Hello"); // type: string
7const num = echo(42);      // type: number
8const obj = echo({ x: 10, y: 20 }); // type: { x: number, y: number }

What's happening here? The <T> tells TypeScript "this is a generic type parameter." When you call the function, TypeScript automatically figures out what T should be based on what you pass in. No type casting needed!

Generic Interfaces: Building Type-Safe Data Structures 🏗️

Let's create a type-safe container that can hold any type of value:

typescript(17 lines)
1interface Container<T> {
2    value: T;
3    timestamp: Date;
4    log(): void;
5}
6
7class DataContainer<T> implements Container<T> {
8    constructor(public value: T, public timestamp: Date = new Date()) {}

Advanced Generic Patterns 🔥

1. Constraining Type Parameters

Sometimes you want to restrict what types can be used with your generic. The extends keyword lets you do this:

typescript
1interface HasLength {
2    length: number;
3}
4
5function logLength<T extends HasLength>(item: T): void {
6    console.log(`Length: ${item.length}`);
7}
8
9// These work fine!
10logLength("Hello");          // strings have length
11logLength([1, 2, 3]);       // arrays have length
12logLength({ length: 10 });   // objects with length property work too
13
14// This would be a type error!
15// logLength(42);  // numbers don't have a length property

2. Generic Type Mapping

Want to transform every property in a type? Generic mapped types are your friend:

typescript(21 lines)
1type ReadOnly<T> = {
2    readonly [K in keyof T]: T[K];
3};
4
5interface User {
6    id: number;
7    name: string;
8    preferences: { theme: string };

3. Conditional Types with Generics

You can even make types that change based on conditions:

typescript
1type IsString<T> = T extends string ? true : false;
2
3// TypeScript knows these types at compile time!
4type A = IsString<"hello">;  // type: true
5type B = IsString<42>;       // type: false
6
7// Real-world example: Extract nullable types
8type NonNullable<T> = T extends null | undefined ? never : T;
9
10type C = NonNullable<string | null | undefined>;  // type: string

Practical Use Cases 💡

1. Type-Safe API Responses

typescript(20 lines)
1interface ApiResponse<T> {
2    data: T;
3    status: number;
4    timestamp: string;
5    error?: string;
6}
7
8interface User {

2. Type-Safe Event Handling

typescript(26 lines)
1type EventMap = {
2    click: { x: number; y: number };
3    change: { oldValue: string; newValue: string };
4    submit: { data: Record<string, unknown> };
5}
6
7class TypedEventEmitter {
8    private listeners: Record<string, Function[]> = {};

Best Practices 📝

  1. Keep It Simple: Start with a single type parameter and add more only when needed.
  2. Use Descriptive Names: Instead of just T, use names like TData or TResponse when the meaning isn't obvious.
  3. Constrain When Possible: Use extends to make your generics more predictable and catch errors earlier.
  4. Document Your Generics: Add JSDoc comments explaining what types are expected and why.

Wrap-Up 🎉

Generics might seem complex at first, but they're an incredibly powerful tool for building type-safe, reusable code. Start with the basics and gradually work your way up to more advanced patterns. Before you know it, you'll be using generics to write more robust and maintainable TypeScript code!

Have questions or want to share your own generic patterns? Drop them in the comments below! Let's learn together. 🙌

Happy coding! 💻✨

About the Author

Ihor (Harry) Chyshkala

Ihor (Harry) Chyshkala

Code Alchemist: Transmuting Ideas into Reality with JS & PHP. DevOps Wizard: Transforming Infrastructure into Cloud Gold | Orchestrating CI/CD Magic | Crafting Automation Elixirs