Understanding TypeScript Generics — Reusable Types Made Simple

If you've ever caught yourself thinking, “I wish I didn’t have to repeat this type again,” — you’re not alone.
When writing TypeScript, we often create functions or components that could work with multiple types, but defining them for each one feels redundant.
That’s where Generics come in.
Think of Generics as type variables — they let you write flexible, reusable code without losing type safety.
💡 What Are Generics?
Generics let you write code that works with any type, while still enforcing strong typing.
In simple terms, Generics make your types dynamic — but in a safe, predictable way.
function identity<T>(value: T): T {
return value
}
const a = identity<string>('Hello')
const b = identity<number>(123)Here, <T> declares a type variable.
Each time the function is called, T becomes the type of the argument.
| Symbol | Meaning |
|---|---|
<T> |
Declares a type variable |
T |
Represents the generic type |
identity<T>(value: T): T |
Takes a type T and returns the same type |
You can think of
<T>as “type parameters,” similar to function parameters — but for types.
🧩 Basic Generic Patterns
1️⃣ Generic Functions
function wrap<T>(value: T) {
return { value }
}
const num = wrap(10) // { value: number }
const str = wrap('Hello') // { value: string }TypeScript automatically infers the type of <T> from the argument.
2️⃣ Generic Interfaces
interface ApiResponse<T> {
data: T
status: number
}
const userResponse: ApiResponse<{ name: string }> = {
data: { name: 'Alex' },
status: 200,
}3️⃣ Generic Classes
class Storage<T> {
private items: T[] = []
add(item: T) {
this.items.push(item)
}
getAll(): T[] {
return this.items
}
}
const stringStore = new Storage<string>()
stringStore.add('Hello')
const numberStore = new Storage<number>()
numberStore.add(100)💬 One class, multiple data types — that’s the real power of Generics.
⚙️ Adding Constraints with extends
Sometimes you want to limit what types can be passed to a generic.
That’s where constraints come in.
function getLength<T extends { length: number }>(value: T) {
return value.length
}
getLength('hello') // ✅ OK (string has length)
getLength([1, 2, 3]) // ✅ OK (array has length)
getLength(123) // ❌ Error (number has no length)The extends keyword ensures that the type passed to T must have a length property.
🧠 Common Real-World Patterns
1️⃣ API Response Types
interface ApiResponse<T> {
success: boolean
data: T
}
function fetchData<T>(url: string): Promise<ApiResponse<T>> {
return fetch(url).then((res) => res.json())
}
type User = { id: number; name: string }
fetchData<User>('/api/user').then((res) => {
if (res.success) {
console.log(res.data.name) // ✅ Type-safe autocomplete
}
})Generics make your API layers safer and more flexible — no
anyneeded.
2️⃣ Reusable Utility Functions
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const result = merge({ name: 'Alex' }, { age: 29 })
// result: { name: string; age: number }Perfect for combining objects or composing configurations safely.
3️⃣ Generics in React Components
type ListProps<T> = {
items: T[]
renderItem: (item: T) => JSX.Element
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>
}
const users = [{ id: 1, name: 'Alex' }, { id: 2, name: 'Brian' }]
<List
items={users}
renderItem={(user) => <li key={user.id}>{user.name}</li>}
/>Generics in React props give you type-safe reusability — especially for shared UI components.
🧩 Combining Generics with keyof
Generics pair beautifully with keyof to build safe property accessors.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Alex', age: 29 }
const name = getProperty(user, 'name') // string
const age = getProperty(user, 'age') // numberThis pattern is used heavily in ORMs, form libraries, and API mappers.
🧠 Default Generic Types
You can also define default types for Generics.
interface ApiResponse<T = unknown> {
data: T
status: number
}
const res: ApiResponse = {
data: 'ok', // T defaults to unknown
status: 200,
}Default types are especially common inside libraries and SDKs where extensibility is key.
📊 Quick Summary
| Concept | Description | Example |
|---|---|---|
| Generic | Dynamic type variable | <T>(value: T) => T |
| extends | Adds type constraints | <T extends { length: number }> |
| keyof | Extracts object keys | <K extends keyof T> |
| Default Type | Sets a default | <T = string> |
💬 Final Thoughts
Generics might look intimidating at first, but once you understand the concept,
they unlock the true power of TypeScript — writing flexible yet safe code.
From APIs and React components to utility functions and data models,
you’ll find Generics everywhere in real-world codebases.
In short, Generics are all about balance: flexibility without losing safety.
Once you start thinking in Generics, you’ll never go back.
#TypeScript #Generics #TypeSafety #ReusableTypes #JavaScript #FrontendDevelopment #React #TypeInference #TypeSystem