TypeScript Generics Basics — Complete Guide
In this tutorial, you will learn about TypeScript Generics Basics. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript generics let you write functions, classes, and types that work with any data type while preserving strict Type Checking — like a reusable machine that adapts its interface to whatever you feed it.
What You'll Learn
- Generic functions and type parameters
- Type parameter naming conventions
- Constraints with
extends - Generic interfaces and type aliases
- Default type parameters
Why It Matters
Without generics, you'd write the same function multiple times for different types (a numberIdentity, a stringIdentity, etc.) or use any and lose type safety. Generics give you the best of both — one function, full type safety, zero duplication.
Real-World Use
React's useState<T>() hook is a generic function — you provide the type, and it returns a properly typed state value and setter. Without generics, every state value would be any. Durga Antivirus Pro uses a generic ApiClient<T> class to type API responses — one class handles User, Scan, Threat, and Report resources with full type safety.
Learning Path
flowchart LR A[Type Assertions] --> B[Generics Basics] B --> C[Advanced Generics] B --> D[You Are Here] C --> E[Conditional Types] E --> F[Mapped Types]
The Problem Generics Solve
// Without generics: one function per type
function identityNumber(arg: number): number { return arg; }
function identityString(arg: string): string { return arg; }
// With any: loses type safety
function identityAny(arg: any): any { return arg; }
const result = identityAny("hello"); // result type is 'any'
Generic Functions
function identity<T>(arg: T): T {
return arg;
}
const num = identity(42); // type: number
const str = identity("hi"); // type: string
const bool = identity(true); // type: boolean
The <T> is a type parameter — a placeholder for the actual type that gets filled in when you call the function. TypeScript infers it from the argument.
Explicit Type Annotation
const num = identity<number>(42);
const str = identity<string>("hello");
Usually you don't need the explicit annotation — inference works. Use it when inference fails or for documentation.
Naming Conventions
| Parameter | Convention | When |
|---|---|---|
<T> |
Type | General purpose |
<K> |
Key | Object keys |
<V> |
Value | Object values |
<E> |
Element | Array elements |
<R> |
Return | Return type |
Working with Generic Arrays
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]); // type: number | undefined
const str = firstElement(["a", "b", "c"]); // type: string | undefined
console.log(firstElement([10, 20, 30])); // 10
console.log(firstElement([])); // undefined
Constraints with extends
Sometimes you need to restrict what types a generic can accept:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // 5 — string has length
logLength([1, 2, 3]); // 3 — array has length
// logLength(42); // Error: number doesn't have length
Think of constraints as saying: "T must be a type that satisfies this interface." This gives you access to properties of the constrained type inside the function.
Constraining to Keys
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30, email: "a@b.com" };
const name = getProperty(user, "name"); // type: string
const age = getProperty(user, "age"); // type: number
// getProperty(user, "ssn"); // Error: "ssn" is not in keyof
Generic Interfaces
interface Repository<T> {
getById(id: string): T | undefined;
getAll(): T[];
create(item: T): void;
update(id: string, item: Partial<T>): void;
delete(id: string): void;
}
class UserRepository implements Repository<User> {
private users: User[] = [];
getById(id: string): User | undefined {
return this.users.find(u => u.id === id);
}
getAll(): User[] { return this.users; }
create(item: User): void { this.users.push(item); }
update(id: string, item: Partial<User>): void {
const index = this.users.findIndex(u => u.id === id);
if (index >= 0) this.users[index] = { ...this.users[index], ...item };
}
delete(id: string): void {
this.users = this.users.filter(u => u.id !== id);
}
}
Generic Type Aliases
type Result<T> = { success: true; data: T } | { success: false; error: string };
function fetchData<T>(url: string): Result<T> {
try {
const data = JSON.parse(localStorage.getItem(url) || "null") as T;
return { success: true, data };
} catch {
return { success: false, error: "Failed to fetch" };
}
}
const result = fetchData<{ name: string }>("/api/user");
if (result.success) {
console.log(result.data.name); // Narrowed
}
Default Type Parameters
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
const arr1 = createArray(3, "hello"); // type: string[]
const arr2 = createArray<number>(3, 42); // type: number[]
Default types are used when TypeScript cannot infer the type parameter.
Multiple Type Parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p1 = pair("Alice", 30); // type: [string, number]
const p2 = pair(1, true); // type: [number, boolean]
Common Mistakes
1. Not Using Constraints When You Need Them
function getLength<T>(arg: T): number {
// return arg.length; // Error: Property 'length' doesn't exist on T
}
function getLength<T extends { length: number }>(arg: T): number {
return arg.length; // OK
}
2. Overusing Generics
// Overkill
function identity<T>(arg: T): T { return arg; }
// Just use the specific type
function identity(arg: string): string { return arg; }
Add generics only when you genuinely need the type to vary.
3. Forgetting to Type Generic Constraints Properly
// Too loose
function process<T>(items: T[]) { }
// More specific
function process<T extends { id: string }>(items: T[]) { }
4. Not Providing Type Arguments When Inference Fails
Sometimes TypeScript cannot infer the type parameter. Provide it explicitly:
const data = await api.get<ScanResult>("/scans/123");
5. Using any Instead of a Generic
// Bad
function clone(obj: any): any { return { ...obj }; }
// Good
function clone<T>(obj: T): T { return { ...obj }; }
Practice Questions
What is the purpose of the
<T>syntax in a generic function? It introduces a type parameter — a placeholder for a type that will be determined at call time.How do you constrain a generic parameter to only accept types with certain properties? Use
extends:function fn<T extends { length: number }>(arg: T)What is a default type parameter and when is it used? A fallback type used when TypeScript cannot infer the type argument:
function fn<T = string>(arg: T)Can a generic function have multiple type parameters? Yes:
function pair<T, U>(a: T, b: U): [T, U]
Challenge: Write a generic map function that takes an array of type T and a transform function (T) => U, and returns an array of type U. Use it with a string array to produce a number array (lengths).
FAQ
Mini Project: Generic Cache
// src/cache.ts
interface CacheEntry<T> {
value: T;
expiresAt: number;
}
class Cache<T> {
private store = new Map<string, CacheEntry<T>>();
private defaultTTL: number;
constructor(defaultTTLMs: number = 60_000) {
this.defaultTTL = defaultTTLMs;
}
set(key: string, value: T, ttlMs?: number): void {
this.store.set(key, {
value,
expiresAt: Date.now() + (ttlMs ?? this.defaultTTL),
});
}
get(key: string): T | undefined {
const entry = this.store.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiresAt) {
this.store.delete(key);
return undefined;
}
return entry.value;
}
has(key: string): boolean {
return this.get(key) !== undefined;
}
clear(): void {
this.store.clear();
}
}
const scanCache = new Cache<{ id: string; threats: string[] }>(30_000);
scanCache.set("scan-001", { id: "scan-001", threats: ["Trojan"] });
console.log(scanCache.get("scan-001")?.threats); // ["Trojan"]
console.log(scanCache.has("scan-001")); // true
What's Next
Now level up with advanced generic patterns:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/08-type-assertions" >}} | Review type assertions |
| {{< ref "/programming-languages/typescript/10-generics-advanced" >}} | Generic classes, conditional types, mapped types |
| {{< ref "/programming-languages/typescript/11-keyof-typeof" >}} | Keyof, typeof, indexed access types |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro