# TypeScript Generics Guide: From Basics to Advanced Patterns

return (
    
      
      
       to restrict what types T can be.' ,
              ,
            ],
          ),
        
      />

      
        [Home](/)
        /
        [Blog](/blog/)
        /
        TypeScript Generics Guide
      

      
        # 
          TypeScript Generics Guide: From Basics to Advanced Patterns
        
        
          TypeScript
          March 7, 2026
          15 min read
        
      

      

        
          ## Why Generics Matter
          
            Generics are TypeScript&apos;s most powerful feature for writing reusable, type-safe code. They let you create functions, classes, and interfaces that work with multiple types without sacrificing the compiler&apos;s ability to catch errors. Instead of falling back to any, generics preserve the actual type flowing through your code.
          

          
            Without generics, you face a constant trade-off: write type-specific code that is safe but rigid, or use any for flexibility and lose type checking entirely. Generics eliminate this dilemma by parameterizing types just like functions parameterize values.
          

          
            This guide covers generics from basic syntax through advanced patterns like conditional types, mapped types, and utility type construction. Every concept includes runnable code examples you can paste into your editor.
          

        

        
          ## Generic Functions: The Foundation
          
            The simplest generic is a function that captures its argument type and uses it in the return type. The type parameter (conventionally named T) acts as a placeholder that gets filled in when the function is called.
          

          
            `// Without generics: loses type information
function firstElement(arr: any[]): any 
  return arr[0];

// With generics: preserves the exact type
function firstElement(arr: T[]): T | undefined 
  return arr[0];

const num = firstElement([1, 2, 3]);    // type: number
const str = firstElement(["a", "b"]);   // type: string
const obj = firstElement([ id: 1 ]);  // type:  id: number `
          
          
            TypeScript infers the type parameter from the argument, so you rarely need to specify it explicitly. When the compiler cannot infer, you can provide it manually:
          

          
            `// Explicit type argument
const result = firstElement(["hello", "world"]);

// Multiple type parameters
function pair(first: A, second: B): [A, B] 
  return [first, second];

const p = pair("age", 30); // type: [string, number]`
          
        

        
          ## Generic Constraints with extends
          
            Unconstrained generics accept any type, which means you cannot access properties on the type parameter. Constraints narrow the set of allowed types so you can safely access specific properties or methods.
          

          
            `// Error: Property 'length' does not exist on type 'T'
function getLength(item: T): number 
  return item.length; // TS error

// Fixed with constraint
function getLength(item: T): number 
  return item.length; // OK - T must have .length

getLength("hello");     // OK: string has .length
getLength([1, 2, 3]);   // OK: array has .length
getLength(42);          // Error: number has no .length`
          
          
            A common pattern is constraining to an interface to ensure objects have required fields:
          

          
            `interface HasId 
  id: string | number;

function findById(items: T[], id: T["id"]): T | undefined 
  return items.find(item => item.id === id);

interface User extends HasId  id: number; name: string; 
interface Product extends HasId  id: string; title: string; 

const users: User[] = [ id: 1, name: "Alice" ];
const found = findById(users, 1); // type: User | undefined`
          
        

        
          ## Generic Interfaces and Type Aliases
          
            Generics are not limited to functions. Interfaces and type aliases can be generic too, which is how you create reusable data structures like API responses, collections, and state containers.
          

          
            `// Generic API response wrapper
interface ApiResponse 
  data: T;
  status: number;
  message: string;
  timestamp: string;

// Usage - the type parameter fills in 'data'
type UserResponse = ApiResponse;
//  data: User; status: number; message: string; timestamp: string; 

type ProductListResponse = ApiResponse;
//  data: Product[]; status: number; ... 

// Generic result type (like Rust's Result)
type Result =
  |  ok: true; value: T 
  |  ok: false; error: E ;

function parseJSON(json: string): Result 
  try 
    return  ok: true, value: JSON.parse(json) ;
   catch (e) 
    return  ok: false, error: e as Error ;
  
`
          
        

        
          ## Generic Classes
          
            Generic classes are essential for building type-safe data structures. The type parameter is available to all instance methods and properties.
          

          
            `class Stack 
  private items: T[] = [];

  push(item: T): void 
    this.items.push(item);
  

  pop(): T | undefined 
    return this.items.pop();
  

  peek(): T | undefined 
    return this.items[this.items.length - 1];
  

  get size(): number 
    return this.items.length;
  

const numberStack = new Stack();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // type: number | undefined

// TypeScript infers generic from constructor args
class Pair 
  constructor(public first: A, public second: B) 

  swap(): Pair 
    return new Pair(this.second, this.first);
  

const p = new Pair("hello", 42);
const swapped = p.swap(); // Pair`
          
        

        
          ## The keyof Operator and Indexed Access Types
          
            The keyof operator produces a union of all property names of a type. Combined with generics, it enables type-safe property access patterns.
          

          
            `// keyof creates a union of property names
type UserKeys = keyof User; // "id" | "name"

// Type-safe property getter
function getProperty(obj: T, key: K): T[K] 
  return obj[key];

const user: User =  id: 1, name: "Alice" ;
const name = getProperty(user, "name");  // type: string
const id = getProperty(user, "id");      // type: number
getProperty(user, "email");              // Error: not a key of User

// Type-safe pick function
function pick(obj: T, keys: K[]): Pick 
  const result =  as Pick;
  keys.forEach(key =>  result[key] = obj[key]; );
  return result;

const partial = pick(user, ["name"]); //  name: string `
          
        

        
          ## Conditional Types
          
            Conditional types select one of two types based on a condition, using the syntax T extends U ? X : Y. They are the type-level equivalent of ternary expressions and unlock powerful type transformations.
          

          
            `// Basic conditional type
type IsString = T extends string ? true : false;
type A = IsString; // true
type B = IsString;      // false

// Practical: extract array element type
type ElementOf = T extends (infer E)[] ? E : never;
type NumEl = ElementOf;  // number
type StrEl = ElementOf;  // string

// Extract return type (simplified ReturnType)
type Return = T extends (...args: any[]) => infer R ? R : never;
type FnReturn = Return string>; // string

// Distributive conditional types
type NonNullable = T extends null | undefined ? never : T;
type Clean = NonNullable; // string`
          
        

        
          ## Mapped Types
          
            Mapped types iterate over the keys of a type and transform each property. They are the backbone of utility types like Partial, Required, Readonly, and Record.
          

          
            `// How Partial works internally
type MyPartial = 
  [K in keyof T]?: T[K];
;

// How Readonly works internally
type MyReadonly = 
  readonly [K in keyof T]: T[K];
;

// Custom: make all properties nullable
type Nullable = 
  [K in keyof T]: T[K] | null;
;

interface Config 
  host: string;
  port: number;
  debug: boolean;

type NullableConfig = Nullable;
//  host: string | null; port: number | null; debug: boolean | null; 

// Key remapping with 'as' (TS 4.1+)
type Getters = 
  [K in keyof T as \`get$Capitalize\`]: () => T[K];
;

type ConfigGetters = Getters;
//  getHost: () => string; getPort: () => number; getDebug: () => boolean; `
          
        

        
          ## Template Literal Types
          
            Template literal types combine string literals with type parameters to create powerful string pattern types. They enable type-safe CSS class names, API routes, event handlers, and more.
          

          
            `// Event handler type
type EventName = "click" | "focus" | "blur";
type Handler = \`on$Capitalize\`;
// "onClick" | "onFocus" | "onBlur"

// API route builder
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = \`/api/$string\`;

// Type-safe CSS class builder
type Size = "sm" | "md" | "lg";
type Color = "red" | "blue" | "green";
type ClassName = \`text-$Size\` | \`bg-$Color\`;`
          
        

        
          ## Real-World Patterns
          
            Here are generic patterns you will use repeatedly in production TypeScript codebases.
          

          ### Type-Safe Event Bus
          
            `type EventMap = 
  "user:login":  userId: string; timestamp: number ;
  "user:logout":  userId: string ;
  "cart:add":  productId: string; quantity: number ;
;

class TypedEventBus> 
  private handlers = new Map>();

  on(
    event: K,
    handler: (data: Events[K]) => void
  ): void 
    if (!this.handlers.has(event as string)) 
      this.handlers.set(event as string, new Set());
    
    this.handlers.get(event as string)!.add(handler);
  

  emit(event: K, data: Events[K]): void 
    this.handlers.get(event as string)?.forEach(fn => fn(data));
  

const bus = new TypedEventBus();
bus.on("user:login", (data) => 
  console.log(data.userId); // fully typed
);`
          

          ### Type-Safe API Client
          
            `// Define API shape
interface ApiEndpoints 
  "/users":  response: User[]; params:  page?: number  ;
  "/users/:id":  response: User; params:  id: number  ;
  "/products":  response: Product[]; params:  category?: string  ;

// Type-safe fetch wrapper
async function api(
  endpoint: T,
  params?: ApiEndpoints[T]["params"]
): Promise 
  const url = new URL(endpoint, "https://api.example.com");
  if (params) 
    Object.entries(params).forEach(([k, v]) => 
      url.searchParams.set(k, String(v));
    );
  
  const res = await fetch(url.toString());
  return res.json();

// Usage - fully typed responses
const users = await api("/users",  page: 1 ); // User[]
const user = await api("/users/:id",  id: 42 ); // User`
          
        

        
          ## Common Mistakes and How to Avoid Them
          
            
              
                
                  Mistake
                  Problem
                  Fix
                
              
              
                
                  Too many type params
                  Unreadable signatures
                  Max 2-3 type params; extract interfaces
                
                
                  Unnecessary generics
                  Complexity without benefit
                  Only use when return type depends on input
                
                
                  Using any as constraint
                  Defeats the purpose
                  Use unknown or a specific interface
                
                
                  Not using defaults
                  Verbose call sites
                  `Add defaults: `
                
                
                  Ignoring variance
                  Unexpected assignability
                  `Use in/out modifiers (TS 4.7+)`
                
              
            
          
        

        
          ## Frequently Asked Questions
          
            
              ### What are generics in TypeScript?
              
                Generics allow you to write functions, classes, and interfaces that work with any type while preserving type safety. Instead of using any, generics capture the actual type passed by the caller, enabling the compiler to enforce correct usage throughout the code. They are TypeScript&apos;s primary mechanism for code reuse without sacrificing type checking.
              

            
            
              ### When should I use generics vs union types?
              
                Use generics when the return type depends on the input type and you need to preserve that relationship through the function. Use union types when you accept a fixed set of types but do not need to track which one was passed. For example, a first() function on arrays should be generic (returns the same element type), while an id field that can be string or number is a union.
              

            
            
              ### What is the difference between extends and implements with generics?
              
                In a generic constraint like ``, extends means the type parameter T must be assignable to HasId. The implements keyword is for classes implementing interfaces and cannot be used in generic constraints. Constraints restrict what types are valid for the type parameter.
              

            
          
        

        
          ## Validate Your TypeScript with BytePane Tools
          
            Working with generic types that produce JSON output? Use our free [JSON Formatter](/json-formatter/) to validate and pretty-print API responses, or test your regex patterns with the [Regex Tester](/regex-tester/).
          

          [
            Open JSON Formatter
          ](/json-formatter/)
        

        
          ## Related Articles
          
            [
              ### TypeScript Utility Types
              Partial, Pick, Omit, Record, and custom utility types explained.

            ](/blog/typescript-utility-types/)
            [
              ### JSON Schema Validation
              Validate data structures with JSON Schema and TypeScript.

            ](/blog/json-schema-validation-guide/)
            [
              ### REST API Design Principles
              Best practices for HTTP methods, error handling, and versioning.

            ](/blog/rest-api-design-principles/)
            [
              ### JWT Tokens Explained
              JWT structure, signing algorithms, and authentication patterns.

            ](/blog/jwt-tokens-guide/)
          
        

      
    
  )

---

*Originally published on [bytepane.com](https://www.bytepane.com/blog/typescript-generics-guide/). Visit for free interactive calculators and tools.*
