DEV Community

Cover image for 🔧 Typescript Generics - Practical Guide and examples
Software Jutsu
Software Jutsu

Posted on

🔧 Typescript Generics - Practical Guide and examples

Generics, reminds me to early days of studying typescript, when i see those T, U, V characters inside < brackets > that scared the hell out of me and i try to avoid them.
once i understand them, they are actually not that scary, they are powerful.

I collected examples that will help you quickly understand why, and how to use generics, without have to read through the complex documentation and confusing explanations. 😭

What are Generics?

Generics let you pass a type as a parameter to functions, interfaces, or classes.

Think of it as a function parameter, but for types.

function doSomething<T>(input: T): T {
  return input;
}
Enter fullscreen mode Exit fullscreen mode

Here, T is a type placeholder. When you use the function, T gets replaced by the actual type you pass.

T is just convention, you can actually name it anything you like,

function doSomething<TTypeBeingPassedHere>(input: TTypeBeingPassedHere): TTypeBeingPassedHere {
  return input;
}
Enter fullscreen mode Exit fullscreen mode

To give distinct identifier, commonly its starts with T followed by the name, here is another example

function doSomething<TInput>(inputOne: TInput): TInput {
  // ... some logic here
  return input;
}

doSomething<number>(1) // returns number
doSomething<string>("1") // returns string
Enter fullscreen mode Exit fullscreen mode

This code defines the function must always return TInput, and it passed by the of the caller,
in this case TInput passed as number hence it will return number

🧐 Why Use Generics?
Avoid using any (which removes type safety)
Avoid code duplication
Write reusable and type-safe utilities

to easier understand how generic works and its benefits, take a look of more examples below.

Common use case scenarios


Reusing Logic Without Losing Types

Scenario: You want to write a utility function that works with any type but still keeps the original type intact.

function wrapInArray<T>(value: T): T[] {
  return [value];
}

const numberArray = wrapInArray(123);     // number[]
const stringArray = wrapInArray('hello'); // string[]
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics?

  • If you used any, you’d lose type safety.
  • If you hardcoded string, you couldn’t reuse it for number.

Handling API Responses

Scenario: You fetch data from different endpoints. All responses follow the same format: data + success. But the data can vary.

interface ApiResponse<T> {
  data: T;
  success: boolean;
}

const userResponse: ApiResponse<{ id: number; name: string }> = {
  data: { id: 1, name: 'Alice' },
  success: true,
};

const postsResponse: ApiResponse<string[]> = {
  data: ['post 1', 'post 2'],
  success: true,
};
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics?

  • You avoid duplicating the structure. ApiResponse can be re-used for different response shape.
  • You get full type safety no matter what data is.

Creating Flexible List Utilities

Scenario: You want a function like getFirstItem, and you want it to work for any array type.

function getFirstItem<T>(list: T[]): T | undefined {
  return list[0];
}

const firstName = getFirstItem(['Rick', 'Morty']); // string
const firstNumber = getFirstItem([10, 20, 30]);     // number
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics?

  • Without generics, you’d either duplicate the function or use any.

Extending Built-in Types Safely

Scenario: You want to write a function that only accepts objects that have a length, like strings or arrays — not numbers or booleans.

function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength('hello');     // valid
logLength([1, 2, 3]);   // valid
logLength(100);         // ❌ Error: number doesn't have length
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics with extends?

  • Combined with use of Extends, you can selectively accepts types of Generics you allow, in this case logLength only accepts anything that compatible with { length: number}
  • You define a flexible type that still enforces structure.

React Hook That Works with Any Type

Scenario: You want a useLocalStorage hook that works with any type of value — string, number, object, etc.

function useLocalStorage<T>(key: string, initialValue: T): [T, (val: T) => void] {
  const [value, setValue] = React.useState<T>(() => {
    const json = localStorage.getItem(key);
    return json ? JSON.parse(json) : initialValue;
  });

  React.useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics?

  • So this hook works with boolean, string, { id: number }, or anything else, with full autocomplete and safety.

Generic Class for Storing Any Type

Scenario: You want a class that stores data of any type.

class Store<T> {
  private items: T[] = [];

  add(item: T) {
    this.items.push(item);
  }

  getAll(): T[] {
    return this.items;
  }
}

const userStore = new Store<{ id: number; name: string }>();
userStore.add({ id: 1, name: 'Alice' });

const numberStore = new Store<number>();
numberStore.add(10);
Enter fullscreen mode Exit fullscreen mode

🧠 Why use generics?

  • One class works for both objects, strings, and numbers - without rewriting the logic.

official reference https://www.typescriptlang.org/docs/handbook/2/generics.html

Top comments (0)