A Simple Guide to TypeScript Generics
Generics are a powerful feature in TypeScript that allows you to write reusable, flexible, and type-safe code by creating components and functions that work with a variety of types while maintaining type safety.
Generics are a powerful feature in TypeScript that allows you to write reusable, flexible, and type-safe code by creating components and functions that work with a variety of types while maintaining type safety. Instead of writing multiple functions that do the same thing but with different types, you can write a single generic function that adapts to the type it receives.
NOTE: This post assumes you have a basic understanding of TypeScript and JavaScript. Generics can be complex at first, but once you understand the concept, they become an invaluable tool in your TypeScript toolkit.
## The Problem Without Generics
Before diving into generics, let's look at why we need them. Consider a function that returns whatever is passed to it:
function (: string): string {
return ;
}
function (: number): number {
return ;
}
function (: boolean): boolean {
return ;
}This is repetitive and not scalable. We could use any, but we lose type safety:
function (: any): any {
return ;
}
const = ('hello');
// We don't know what type output is, which defeats TypeScript's purpose## Basic Generic Syntax
With generics, we can create a single function that works with any type while preserving type information:
function <>(: ): {
return ;
}
// TypeScript can infer the type
const = ('hello'); // type is string
const = (42); // type is number
const = (true); // type is boolean
// You can also explicitly specify the type
const = <string>('hello'); // type is stringThe <T> is a type variable - a placeholder for a type that will be specified when the function is called. You can think of it like a function argument, but for types instead of values.
## Generic Interfaces
You can also use generics with interfaces:
interface <> {
: ;
}
const : <string> = {
: 'Hello, Generics!'
};
const : <number> = {
: 42
};
// You can even nest generics
const : <<string>> = {
: {
: 'Nested box!'
}
};## Generic Classes and Functional Alternatives
While classes can be generic, modern TypeScript/JavaScript often prefers functional patterns:
### Class-based approach (traditional):
class <> {
private : [] = [];
(: ): void {
this..();
}
(: number): | undefined {
return this.[];
}
(): [] {
return [...this.];
}
}
// Usage
const = new <string>();
.('Item 1');
.('Item 2');
const = new <number>();
.(1);
.(2);### Functional approach (modern):
// Factory function with generics
function <>() {
let : [] = [];
return {
(: ): void {
.();
},
(: number): | undefined {
return [];
},
(): [] {
return [...];
}
};
}
// Usage - same as class but with functional pattern
const = <string>();
.('Item 1');
.('Item 2');
const = <number>();
.(1);
.(2);## Generic Constraints
Sometimes you want to constrain a generic to only work with certain types. You can do this using the extends keyword:
interface Lengthwise {
: number;
}
function < extends Lengthwise>(: ): void {
.(.);
}
('hello'); // Works - strings have a length property
([1, 2, 3]); // Works - arrays have a length property
({ : 10, : 'test' }); // Works - has length property
// logLength(42); // Error - numbers don't have a length propertyYou can also constrain to specific object shapes:
interface WithId {
: number;
}
function < extends WithId>(: [], : number): | undefined {
return .(() => . === );
}
const = [
{ : 1, : 'Alice' },
{ : 2, : 'Bob' }
];
const = (, 1); // type is { id: number; name: string; } | undefined## Using Type Parameters in Generic Constraints
You can reference one type parameter in the constraint of another:
function <, extends keyof >(: , : ): [] {
return [];
}
const = {
: 'Alice',
: 30,
: 'alice@example.com'
};
const = (, 'name'); // type is string
const = (, 'age'); // type is number
// const invalid = getProperty(user, "invalid"); // Error - "invalid" is not a key of userThis is extremely powerful for creating type-safe property access.
## Generic Utility Types
TypeScript provides several built-in generic utility types:
### Partial<T> - Makes all properties optional
interface User {
: number;
: string;
: string;
}
function (: number, : <User>): void {
// updates can have any subset of User properties
const : User = { , : '', : '' };
.(, );
}
(1, { : 'New Name' }); // Works### Required<T> - Makes all properties required
interface PartialUser {
?: number;
?: string;
?: string;
}
type = <PartialUser>;
// All properties are now required### Pick<T, K> - Select specific properties
interface User {
: number;
: string;
: string;
: string;
}
type = <User, 'id' | 'name' | 'email'>;
// PublicUser has only id, name, and email### Omit<T, K> - Remove specific properties
interface User {
: number;
: string;
: string;
: string;
}
type = <User, 'password'>;
// PublicUser has everything except password## Real-World Example: API Response Handler
Here's a practical example of using generics for API handling:
interface <> {
: ;
: number;
: string;
}
interface User {
: number;
: string;
: string;
}
interface Post {
: number;
: string;
: string;
}
async function <>(: string): <<>> {
const = await ();
const = await .();
return {
,
: .,
: .
};
}
// Usage
async function (: number) {
const = await <User>(`/api/users/${}`);
// response.data is typed as User
.(..);
}
async function (: number) {
const = await <Post>(`/api/posts/${}`);
// response.data is typed as Post
.(..);
}## Real-World Example: Generic Repository Pattern
Using a functional approach with TypeScript generics:
interface Entity {
: number;
}
// Functional repository definition using TypeScript generics
interface < extends Entity> {
(: number): < | null>;
(): <[]>;
(: <, 'id'>): <>;
(: number, : <>): < | null>;
(: number): <boolean>;
}
// Factory function to create a repository
function < extends Entity>(: [] = []): <> {
let : [] = [...];
return {
async (: number): < | null> {
return .(() => . === ) || null;
},
async (): <[]> {
return [...];
},
async (: <, 'id'>): <> {
const : = {
: . + 1,
...
} as ;
.();
return ;
},
async (: number, : <>): < | null> {
const = .(() => . === );
if ( === -1) return null;
[] = { ...[], ... };
return [];
},
async (: number): <boolean> {
const = .(() => . === );
if ( === -1) return false;
.(, 1);
return true;
}
};
}
// Usage example
interface User extends Entity {
: string;
: string;
}
// Create a user repository using the factory function
const = <User>([{ : 1, : 'Alice', : 'alice@example.com' }]);
// Use the repository methods
async function () {
const = await .(1);
const = await .();
const = await .({ : 'Bob', : 'bob@example.com' });
const = await .(1, { : 'Alice Updated' });
const = await .(1);
}## Best Practices
-
Prefer functional patterns: Modern TypeScript/JavaScript favors functional programming patterns over classes. Use factory functions and composition over class inheritance where possible.
-
Use descriptive type parameter names: While
Tis conventional, use more descriptive names when appropriate:<T, U, V> // For multiple type parameters <TKey, TValue> // For key-value pairs <TComponent> // For React components -
Use constraints when needed: Don't make your generic too permissive. Use
extendsto enforce requirements. -
Consider defaults: You can provide default types for generics:
interface Box<T = string> { contents: T; } const defaultBox: Box = { contents: 'hello' }; // T is string -
Don't overuse: Not everything needs to be generic. Use generics when you genuinely need type flexibility.
## Conclusion
Generics are a fundamental tool in TypeScript that allows you to write reusable, type-safe code. They enable you to create functions, interfaces, and classes that work with a variety of types while maintaining full type safety. By mastering generics, you'll write more maintainable and flexible code that catches errors at compile time rather than runtime.
Published on January 5, 2026
8 min read
Found an Issue!
Find an issue with this post? Think you could clarify, update or add something? All my posts are available to edit on Github. Any fix, little or small, is appreciated!
Edit on GitHubLast updated on
