← Back to all articles

TypeScript Generics for Beginners

August 15, 2026By Kazi Samiul Haque Adrik

The "any" Trap

When developers first transition from JavaScript to TypeScript, they usually love it—until they encounter a function that needs to handle multiple data types. At that point, frustration sets in, and they inevitably type any to silence the compiler errors.

function getFirstItem(arr: any[]): any {
  return arr[0];
}

const num = getFirstItem([1, 2, 3]); // num is 'any'
const str = getFirstItem(["a", "b", "c"]); // str is 'any'

While this code runs, you have completely defeated the purpose of TypeScript. The compiler no longer knows if num is a number or if str is a string, meaning you lose all autocompletion and type safety.

This is exactly the problem that Generics were invented to solve.

What are Generics?

A Generic is simply a Type Variable. Just like a normal variable holds a value (like let x = 5), a Type Variable holds a Type (like T = number).

Instead of hardcoding a specific type, you allow the developer calling the function to pass the type in dynamically at runtime.

Here is the exact same function rewritten using a Generic:

// The <T> declares a type variable named 'T'
function getFirstItem<T>(arr: T[]): T {
  return arr[0];
}

// Now TypeScript is incredibly smart!
const num = getFirstItem<number>([1, 2, 3]); // num is explicitly 'number'
const str = getFirstItem<string>(["a", "b", "c"]); // str is explicitly 'string'

Type Inference (The Magic Part)

Writing <number> or <string> every time you call a function is tedious. Fortunately, TypeScript is incredibly smart. It looks at the argument you passed in, figures out the type, and automatically sets the Generic variable for you.

// We didn't pass <number>, but TS infers that T = number because we passed an array of numbers!
const autoNum = getFirstItem([1, 2, 3]); 

Real-World Use Case: API Responses

The most common and powerful use case for Generics in modern web development is handling API responses.

Imagine you have a fetchData function. Sometimes it fetches Users, sometimes it fetches Products. You want the function to return a strongly typed response depending on what you asked for.

First, define your interfaces:

interface User {
  id: string;
  name: string;
}

interface Product {
  id: string;
  price: number;
}

Next, write a Generic fetch function. Notice how we pass <T> to the native Promise object!

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  const data = await response.json();
  return data as T;
}

Finally, call the function and tell it what type to expect:

// TypeScript knows 'user' has an 'id' and 'name'
const user = await fetchData<User>('/api/users/1');

// TypeScript knows 'product' has an 'id' and 'price'
const product = await fetchData<Product>('/api/products/1');

Conclusion

Generics look intimidating because of the <T> syntax, but they are conceptually very simple: they are just variables for types. By mastering Generics, you can write highly reusable, completely type-safe utility functions and API handlers, completely eliminating the need for any in your codebase.

Available for projects
Bangladesh
SSC '26 Grad