Modern examples of diverse examples of TypeScript generics
Real-world examples of diverse examples of TypeScript generics
Let’s start where most developers actually encounter generics: in day‑to‑day application code. Instead of toy snippets, we’ll walk through real examples of TypeScript generics that solve problems you probably recognize.
Generic API client: typed requests and responses
A very common example of TypeScript generics in production code is a typed API client. You want to describe both the request payload and the response shape without duplicating types everywhere.
// One generic for the response, one for the request body
async function apiRequest<TResponse, TBody = undefined>(
url: string,
options?: {
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
body?: TBody;
}
): Promise<TResponse> {
const res = await fetch(url, {
method: options?.method ?? 'GET',
body: options?.body ? JSON.stringify(options.body) : undefined,
headers: {
'Content-Type': 'application/json'
}
});
if (!res.ok) {
throw new Error(`Request failed with status ${res.status}`);
}
return res.json() as Promise<TResponse>;
}
// Usage: strongly typed response + body
interface CreateUserBody {
email: string;
name: string;
}
interface UserDto {
id: string;
email: string;
name: string;
}
async function createUser(body: CreateUserBody) {
const user = await apiRequest<UserDto, CreateUserBody>('/api/users', {
method: 'POST',
body
});
// `user` is fully typed as UserDto here
return user;
}
This is one of the best examples of diverse examples of TypeScript generics used to reduce duplication while preserving type safety. The generic parameters stay close to the call site, and your editor can infer them in many cases.
Generic React components with constrained props
Modern React codebases (especially in 2024 with wide adoption of TypeScript in front‑end teams) lean heavily on generics. A simple but powerful example of TypeScript generics is a reusable table component that understands the shape of its rows.
// Constrain TRow to be an object with string keys
interface TableProps<TRow extends Record<string, any>> {
rows: TRow[];
columns: {
key: keyof TRow;
header: string;
render?: (value: TRow[keyof TRow], row: TRow) => React.ReactNode;
}[];
}
function Table<TRow extends Record<string, any>>({ rows, columns }: TableProps<TRow>) {
return (
<table>
<thead>
<tr>
{columns.map(col => (
<th key={String(col.key)}>{col.header}</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
{columns.map(col => {
const value = row[col.key];
return (
<td key={String(col.key)}>
{col.render ? col.render(value, row) : String(value)}
</td>
);
})}
</tr>
))}
</tbody>
</table>
);
}
// Usage: autocomplete for `key` and `render` arguments
interface ProductRow {
id: string;
name: string;
price: number;
}
<Table<ProductRow>
rows={products}
columns=[
{ key: 'name', header: 'Product' },
{ key: 'price', header: 'Price', render: value => `$${value.toFixed(2)}` }
]
/>
Here you see a practical example of diverse examples of TypeScript generics where the generic type TRow flows through the entire component. The compiler guarantees that key is always a valid property and that render receives the correct value type.
Generic repository pattern backed by a database
In back‑end or full‑stack TypeScript, a generic repository is a very common example of generics that keeps data access code consistent.
interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}
interface RepositoryOptions<T extends BaseEntity> {
tableName: string;
hydrate?: (row: any) => T;
}
class Repository<T extends BaseEntity> {
constructor(private options: RepositoryOptions<T>) {}
async findById(id: string): Promise<T | null> {
const row = await db.select('*')
.from(this.options.tableName)
.where({ id })
.first();
if (!row) return null;
return this.options.hydrate ? this.options.hydrate(row) : (row as T);
}
async insert(entity: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<T> {
const now = new Date();
const row = {
...entity,
id: crypto.randomUUID(),
createdAt: now,
updatedAt: now
} as T;
await db(this.options.tableName).insert(row);
return row;
}
}
// Usage
interface UserEntity extends BaseEntity {
email: string;
role: 'admin' | 'user';
}
const userRepo = new Repository<UserEntity>({ tableName: 'users' });
const user = await userRepo.findById('123'); // typed as UserEntity | null
This pattern gives you one generic implementation that adapts to any entity type while preserving strong typing on queries and results.
Utility types: conditional and mapped generics
Some of the most powerful examples of diverse examples of TypeScript generics live in utility types. If you’ve used Partial<T> or Pick<T, K>, you’ve already benefited from this style.
Here’s a custom utility that makes properties of a type writable, even if they were readonly:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface ReadonlyUser {
readonly id: string;
readonly email: string;
}
type EditableUser = Mutable<ReadonlyUser>;
// Now id and email are writable
And here’s an example of a conditional generic that filters keys by value type:
type KeysOfType<T, ValueType> = {
[K in keyof T]: T[K] extends ValueType ? K : never;
}[keyof T];
interface Settings {
darkMode: boolean;
fontSize: number;
language: string;
}
// Only keys whose values are boolean
type BooleanKeys = KeysOfType<Settings, boolean>; // 'darkMode'
These patterns show how examples of generics go beyond functions and classes. They shape your type system itself, and modern libraries in 2024 lean heavily on these ideas to provide rich, inferred types.
Typed events and message buses
Event emitters and message buses are another area where examples of diverse examples of TypeScript generics shine. You want a central place that knows the mapping from event names to payload types.
interface AppEvents {
'user:created': { id: string; email: string };
'user:deleted': { id: string };
'order:placed': { id: string; total: number };
}
class EventBus<TEvents extends Record<string, any>> {
private handlers: {
[K in keyof TEvents]?: ((payload: TEvents[K]) => void)[];
} = {};
on<K extends keyof TEvents>(event: K, handler: (payload: TEvents[K]) => void) {
const list = this.handlers[event] ?? (this.handlers[event] = []);
list.push(handler as any);
}
emit<K extends keyof TEvents>(event: K, payload: TEvents[K]) {
this.handlers[event]?.forEach(handler => handler(payload));
}
}
const bus = new EventBus<AppEvents>();
bus.on('user:created', payload => {
// payload is { id: string; email: string }
});
bus.emit('user:created', { id: '1', email: 'test@example.com' });
// TypeScript will complain if you forget email or add extra fields
This is a clean example of TypeScript generics enforcing consistency across your codebase without runtime overhead.
Safer configuration objects with generic schemas
Configuration validation is a subtle pain point in many apps. Libraries like Zod and @sinclair/typebox gained popularity by offering runtime schemas plus TypeScript types. You can sketch a lighter‑weight, generic pattern yourself.
interface ConfigField<T> {
env: string;
parse: (value: string) => T;
}
type ConfigSchema = Record<string, ConfigField<any>>;
type InferConfig<TSchema extends ConfigSchema> = {
[K in keyof TSchema]: TSchema[K] extends ConfigField<infer TValue>
? TValue
: never;
};
function loadConfig<TSchema extends ConfigSchema>(schema: TSchema): InferConfig<TSchema> {
const result: any = {};
for (const key in schema) {
const field = schema[key];
const raw = process.env[field.env];
if (raw == null) throw new Error(`Missing env var ${field.env}`);
result[key] = field.parse(raw);
}
return result;
}
const schema = {
port: { env: 'PORT', parse: Number },
nodeEnv: { env: 'NODE_ENV', parse: String },
enableMetrics: { env: 'ENABLE_METRICS', parse: v => v === 'true' }
} satisfies ConfigSchema;
const config = loadConfig(schema);
// config is typed as { port: number; nodeEnv: string; enableMetrics: boolean }
This example of generics shows how you can infer a configuration type from a schema definition, keeping the source of truth in one place.
Narrowing domain models with generic brands
In 2024, more teams are using branded types to avoid mixing up IDs or opaque strings. Generics make this pattern ergonomic.
type Brand<T, TBrand extends string> = T & { readonly __brand: TBrand };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function asUserId(value: string): UserId {
return value as UserId;
}
function asOrderId(value: string): OrderId {
return value as OrderId;
}
// Example of using branded IDs in a repository
interface Order {
id: OrderId;
userId: UserId;
total: number;
}
function getOrderById(id: OrderId): Promise<Order | null> {
// ...
return Promise.resolve(null);
}
const userId: UserId = asUserId('user-1');
const orderId: OrderId = asOrderId('order-1');
getOrderById(userId); // Type error – prevents mixing IDs
This is a subtle example of diverse examples of TypeScript generics that pays dividends in large systems where accidental ID mixups can cause hard‑to‑trace bugs.
Trends in TypeScript generics for 2024–2025
Looking at open‑source projects and large codebases in 2024 and 2025, a few patterns stand out when you examine examples of diverse examples of TypeScript generics:
- Libraries push more logic into types using conditional and variadic tuple generics, especially in frameworks and ORMs.
- Form builders, query builders, and router libraries use generics heavily to infer return types from configuration.
- Teams are increasingly standardizing on stricter
tsconfigsettings (strict,noUncheckedIndexedAccess), which makes well‑designed generics more valuable.
If you want to sanity‑check how far you should push your own generic types, it’s worth looking at how big projects like the TypeScript standard library or popular frameworks structure their declarations. Microsoft’s official TypeScript docs at typescriptlang.org include many real examples that mirror patterns used in production.
For developers coming from more formally typed backgrounds, it can also be helpful to compare these ideas with research and teaching materials from universities. While not TypeScript‑specific, resources like MIT’s open courseware on type systems at mit.edu give context for why generics behave the way they do.
Patterns and anti‑patterns in examples of TypeScript generics
Looking across these examples of diverse examples of TypeScript generics, a few patterns keep showing up.
Good patterns
- Keep type parameters small in number. Most real examples include one to three generics. Past that, readability drops fast.
- Constrain generics when possible.
T extends Record<string, any>orK extends keyof Tgives the compiler more to work with and improves editor hints. - Infer instead of annotate when you can. Many of the best examples let TypeScript infer
Tfrom arguments instead of forcing callers to spell it out. - Use descriptive names.
TRow,TEntity,TResponseare easier to reason about thanT,U,Veverywhere.
Things to watch out for
- Over‑engineering. If a generic type takes a paragraph to explain, it might be doing too much.
- Leaky abstractions. A generic that exposes internal details (like database column names) will lock you in and hurt refactors.
- Type‑level performance. Extremely nested conditional types can slow down editor responsiveness. If your IDE starts lagging on every keypress, consider simplifying.
The examples of TypeScript generics above aim to hit a balance: strong typing, clear intent, and minimal ceremony.
FAQ about examples of TypeScript generics
Q: What are some practical examples of TypeScript generics I can copy into my project today?
Real examples include a generic API client (apiRequest<TResponse, TBody>), a typed event bus (EventBus<TEvents>), generic repositories for entities (Repository<T extends BaseEntity>), and utility types like Mutable<T> or KeysOfType<T, ValueType>. Each example of generics here is designed to be copy‑paste friendly and adaptable.
Q: How many type parameters are reasonable in real‑world generic code?
Most production examples of TypeScript generics use one to three type parameters. If you find yourself adding a fourth or fifth, it’s often a sign that the abstraction should be split into smaller pieces.
Q: Do generics impact runtime performance in TypeScript?
No. Generics exist only at compile time. After TypeScript compiles to JavaScript, all type parameters disappear. Any performance differences come from runtime logic, not from the presence of generics.
Q: Where can I see more real examples of advanced generics?
The TypeScript handbook and reference on typescriptlang.org include many examples of diverse examples of TypeScript generics used in the standard library. University‑level resources on type systems, such as those shared via mit.edu, can also deepen your understanding of the theory behind generics, even though they aren’t TypeScript‑specific.
Q: How do I introduce generics into an existing JavaScript codebase migrating to TypeScript?
Start with the highest‑leverage areas: API clients, data access layers, and shared utilities. Add generics where they clearly reduce duplication or catch real bugs. Over time, you’ll build up your own library of examples of diverse examples of TypeScript generics tailored to your domain, instead of sprinkling types randomly across the codebase.
Related Topics
Modern examples of diverse examples of TypeScript decorators
Modern examples of diverse examples of TypeScript generics
Modern examples of TypeScript integration with Node.js examples
Practical examples of TypeScript configuration examples for modern projects
Modern examples of TypeScript integration with React examples
Explore More TypeScript Code Snippets
Discover more examples and insights in this category.
View All TypeScript Code Snippets