{"post":{"slug":"typescript-generics-explained-2026","title":"TypeScript generics explained in 2026: write once, type everything","excerpt":"Generics are the feature that separates TypeScript beginners from engineers who can write reusable, fully type-safe utilities. Here is the clear 2026 guide.","category":"code","date":"April 8, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">G</span>enerics are the mechanism TypeScript uses to let you write code that is both reusable across types and still fully type-safe. Without generics, you either repeat yourself (one function per type) or lose type information (use `any`). With generics, you write the function once and let the type system track the specific type through every call.\n\n## What changed in 2026\n\n- **TypeScript 5.x brought const type parameters and improved inference.** `const` generics let you capture literal types rather than widened types, reducing the need for `as const` casts in many patterns.\n- **`satisfies` + generics patterns matured.** The combination of `satisfies` (TS 4.9) with generic constraints gives you validation without losing the literal type — widely used in config and schema libraries.\n- **Template literal types in generics became practical.** Building typed route parameters, event names, and CSS properties using template literal types is now a real production pattern, not just a party trick.\n- **Variance annotations (`in`, `out`) were added in TS 4.7** and are now part of standard library type definitions, making complex generic hierarchies more predictable.\n\n## The basics: a generic function\n\nWithout generics:\n```typescript\nfunction first(arr: number[]): number | undefined {\n  return arr[0];\n}\n// Only works for number[]. You'd need to duplicate for string[], etc.\n```\n\nWith a generic:\n```typescript\nfunction first<T>(arr: T[]): T | undefined {\n  return arr[0];\n}\n\nconst n = first([1, 2, 3]);         // n: number | undefined\nconst s = first(['a', 'b', 'c']);   // s: string | undefined\nconst u = first([]);                // u: undefined\n```\n\nTypeScript infers `T` from the argument — you don't write `first<number>([1, 2, 3])` unless inference fails.\n\n## Type constraints with `extends`\n\n```typescript\n// Without constraint — you can't access .id because T might not have it\nfunction getById<T>(items: T[], id: unknown): T | undefined {\n  return items.find((item) => (item as any).id === id); // unsafe!\n}\n\n// With constraint — T must have an id property\nfunction getById<T extends { id: string | number }>(\n  items: T[],\n  id: T['id']\n): T | undefined {\n  return items.find((item) => item.id === id);\n}\n\n// Now TypeScript knows item.id exists and is string | number\nconst user = getById(users, 'user_123');  // typed correctly\n```\n\n`T extends { id: string | number }` is not a runtime check — it's a compile-time constraint that tells TypeScript what properties to expect on `T`.\n\n## Generic interfaces and types\n\n```typescript\n// A typed API response wrapper\ninterface ApiResponse<T> {\n  data: T;\n  status: number;\n  message: string;\n}\n\n// A typed key-value store\ntype Store<K extends string, V> = {\n  get(key: K): V | undefined;\n  set(key: K, value: V): void;\n  keys(): K[];\n};\n\n// Real usage\nasync function fetchUser(id: string): Promise<ApiResponse<User>> {\n  const res = await fetch(`/api/users/${id}`);\n  return res.json();\n}\n// TypeScript knows response.data is a User\n```\n\n## Conditional types and `infer`\n\nConditional types let you compute a type based on another:\n\n```typescript\n// Extract the resolved type from a Promise\ntype Awaited<T> = T extends Promise<infer U> ? U : T;\n\ntype A = Awaited<Promise<string>>;  // string\ntype B = Awaited<number>;           // number (not a Promise, returns as-is)\n\n// Extract function return type\ntype ReturnType<T extends (...args: any) => any> =\n  T extends (...args: any) => infer R ? R : never;\n\n// Extract the element type from an array\ntype ElementType<T extends any[]> =\n  T extends (infer E)[] ? E : never;\n\ntype Elem = ElementType<string[]>;  // string\n```\n\n`infer` inside a conditional type creates a new type variable that captures a part of the matched type. It is the foundation of most utility types in the TypeScript standard library.\n\n## Utility types you should know\n\n| Utility type | What it does |\n| --- | --- |\n| `Partial<T>` | Makes all properties optional |\n| `Required<T>` | Makes all properties required |\n| `Readonly<T>` | Makes all properties readonly |\n| `Pick<T, K>` | Keeps only keys K from T |\n| `Omit<T, K>` | Removes keys K from T |\n| `Record<K, V>` | Object type with keys K and values V |\n| `ReturnType<F>` | Return type of function F |\n| `Parameters<F>` | Parameter tuple of function F |\n| `Awaited<T>` | Unwraps Promise type |\n| `NonNullable<T>` | Removes null and undefined from T |\n\n## How to pick\n\n1. **Does your function work identically for multiple types?** Add a generic. If it only ever handles `string`, just type it as `string`.\n2. **Do you need to access properties on the type parameter?** Add a constraint (`T extends { ... }`). Without it, TypeScript won't know what's available.\n3. **Does a return type depend on an input type in a non-trivial way?** Use conditional types or function overloads.\n4. **Is the generic getting hard to read?** Name it meaningfully (`TItem`, `TKey`, `TResponse`) instead of single letters beyond `T` in simple cases.\n5. **Are you using `any` inside a generic?** That's a sign the generic isn't doing its job — tighten the constraint or restructure the function.\n\n## Common mistakes\n\n**Using generics for a function with one concrete type.** `function parse<T>(json: string): T` is not safer than `function parse(json: string): unknown` — in fact it's less safe, because the caller asserts the type without any check. Use `unknown` and validate.\n\n**Overly loose constraints.** `T extends object` is almost as weak as `T extends any` for most purposes — it excludes primitives but tells you nothing about shape. Constrain to the actual shape you need.\n\n**Forgetting to constrain key generics.** `function pluck<T, K>(obj: T, key: K)` has no constraint relating `K` to `T`. Use `K extends keyof T` to make TypeScript verify the key exists.\n\n**Using generics to avoid writing a union type.** Sometimes `string | number` is clearer than `T extends string | number`. Generics add complexity; only use them when the type needs to flow through to the output.\n\n## What to skip\n\n- **Deeply nested conditional types in application code.** They're powerful in library code but become maintenance nightmares in application logic. If you need more than two levels of conditional types, consider a different approach.\n- **Generic classes for simple data bags.** A generic `Container<T>` is sometimes overkill where a typed interface would do. Classes add runtime cost; interfaces are erased.\n- **Fighting inference with explicit type arguments.** TypeScript's inference is good. `first<string>(['a', 'b'])` is unnecessary — `first(['a', 'b'])` infers correctly. Only provide explicit type arguments when inference actually fails.\n\n## FAQ\n\n**What is the difference between `T extends any` and `T`?**\nEffectively none for constraints. `T extends any` is a vacuous constraint — everything extends `any`. Omit it; just write `T`.\n\n**When should I use `unknown` vs a generic?**\nUse `unknown` when you don't know the type and the function doesn't need to return it in a typed way. Use a generic when the type of the output depends on the type of the input — the function \"passes through\" the type information.\n\n**What are variance annotations (`in`, `out`)?**\n`in` marks a type parameter as contravariant (only used in input positions); `out` marks it as covariant (only used in output positions). They improve type-checking performance and accuracy for complex generic types. Used primarily in library code, not typical application code.\n\n**How do I type a function that accepts any object and returns a deep partial version?**\nUse a recursive conditional type: `type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }`. This is a common library utility; many type libraries export it so you don't need to write it yourself.\n\n## Where to go next\n\n[gRPC vs REST in 2026](/blog/grpc-vs-rest-2026), [GraphQL vs REST in 2026](/blog/graphql-vs-rest-2026), and [API rate limiting in 2026](/blog/api-rate-limiting-2026).","hue":318,"metaTitle":"TypeScript Generics Explained 2026: Practical Guide","metaDescription":"TypeScript generics explained clearly for 2026. Type constraints, conditional types, infer, utility types, and real-world patterns with code examples.","keywords":["typescript generics explained 2026","typescript generics tutorial","typescript generic constraints","conditional types typescript","typescript utility types","typescript infer keyword","generic functions typescript","typescript advanced types","typescript reusable types","typescript 5 generics 2026"],"tldr":["**Generics let you write code that works across types without losing type safety** — instead of `any`, you capture the type and propagate it.","**Type constraints (`extends`) narrow what a generic can accept** — essential when you need to access properties on the type parameter.","**Conditional types and infer unlock the advanced patterns** — utility types like `ReturnType`, `Awaited`, and `Parameters` all use these.","**Know when generics are overkill** — one-off functions with fixed types need no generic; add them when you have real reuse across types.","**Skip:** using generics as a way to avoid defining types — if you're just using `T` everywhere and never constraining it, you may just need `unknown`."],"readTime":"6 min read","wordCount":1279},"related":[{"slug":"how-to-learn-typescript-2026","title":"How to learn TypeScript in 2026: from JavaScript to typed confidence","excerpt":"TypeScript is now the default language for serious JavaScript projects. Here is the practical 2026 path to learning it without getting lost in complexity.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":313},{"slug":"bounded-contexts-explained-2026","title":"Bounded Contexts Explained 2026: Drawing Domain Lines","excerpt":"A bounded context is the boundary where a domain model's vocabulary stays consistent, and outside it the same word can mean something else entirely. Here is how to classify subdomains and draw the lines well in 2026.","category":"code","date":"July 30, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":316},{"slug":"polymorphism-explained-2026","title":"Polymorphism explained in 2026: one interface, many behaviors","excerpt":"Polymorphism is the object-oriented idea that lets one call do different things depending on the type behind it. Here is the 2026 guide to the kinds of polymorphism and when each one earns its keep.","category":"code","date":"June 30, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":240}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"traditional-vs-roth-401k-2026","title":"Traditional vs Roth 401k in 2026: which pretax or after-tax option wins?","excerpt":"Traditional 401k lowers your tax bill today; Roth 401k gives you tax-free income in retirement. Choosing between them is a bet on your future tax rate.","category":"finance","date":"April 8, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":280},"older":{"slug":"umbrella-insurance-explained-2026","title":"Umbrella insurance explained in 2026: who needs it and how much to get","excerpt":"Umbrella insurance is one of the cheapest ways to protect everything you have built. Here is exactly who needs it, how it works, and what a policy actually covers in 2026.","category":"finance","date":"April 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":312}}