TypeScript Generics Advanced Patterns & Constraints

Zaheer Ahmad 5 min read min read
Python
TypeScript Generics Advanced Patterns & Constraints

Introduction

TypeScript generics are one of the most powerful features of TypeScript, allowing developers to write flexible, reusable, and type-safe code. In this typescript generics tutorial, we go beyond the basics and explore advanced TypeScript generics patterns and constraints that are widely used in real-world applications.

For Pakistani students in cities like Lahore, Karachi, and Islamabad—especially those preparing for software engineering roles or freelancing—understanding advanced generics can significantly improve code quality and employability. Whether you are building APIs, handling complex data structures, or working with frameworks like React, generics help you write scalable and maintainable code.

In this tutorial, we will explore constraints, conditional types, mapped types, inference with infer, and real-world patterns used in production systems.

Prerequisites

Before diving into advanced generics, you should be comfortable with:

  • Basic TypeScript syntax (types, interfaces, functions)
  • Basic generics (<T>)
  • Functions and arrow functions in JavaScript/TypeScript
  • Object and array manipulation
  • ES6 features (spread operator, destructuring)
  • Understanding of keyof and typeof

If you’re new, first read the TypeScript Basics tutorial on theiqra.edu.pk.


Core Concepts & Explanation

Generic Constraints with extends

Generic constraints allow you to restrict the types that can be used with generics.

function printLength<T extends { length: number }>(item: T): number {
  return item.length;
}

Line-by-line explanation:

  • function printLength<T extends { length: number }>
    → Defines a generic type T but restricts it to types that have a length property.
  • (item: T)
    → Accepts a parameter of type T.
  • return item.length;
    → Safe to access length because of the constraint.

Usage:

printLength("Ahmad");       // ✅ string has length
printLength([1, 2, 3]);     // ✅ array has length
printLength(100);           // ❌ number doesn't have length

Conditional Types & infer Keyword

Conditional types allow types to depend on conditions.

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

Explanation:

  • T extends (...args: any[]) => infer R
    → Checks if T is a function.
  • infer R
    → Extracts the return type into R.
  • ? R : never
    → If true, return R; otherwise never.

Example:

type Fn = () => string;
type Result = GetReturnType<Fn>; // string

Mapped Types with Key Remapping (as)

Mapped types allow transforming object types.

type PrefixKeys<T> = {
  [K in keyof T as `prefix_${string & K}`]: T[K];
};

Explanation:

  • [K in keyof T]
    → Loop over all keys in T.
  • as 'prefix_${string & K}'
    → Rename keys using template literals.
  • T[K]
    → Preserve original value types.

Example:

type User = {
  name: string;
  age: number;
};

type PrefixedUser = PrefixKeys<User>;

Result:

{
  prefix_name: string;
  prefix_age: number;
}

Distributive Conditional Types

When conditional types are applied to unions, they distribute over each member.

type ToArray<T> = T extends any ? T[] : never;

Example:

type Result = ToArray<string | number>;

Becomes:

string[] | number[]


Practical Code Examples

Example 1: Advanced API Response Handler

type ApiResponse<T> = {
  data: T;
  status: number;
  success: boolean;
};

function fetchData<T>(data: T): ApiResponse<T> {
  return {
    data,
    status: 200,
    success: true,
  };
}

Line-by-line explanation:

  • type ApiResponse<T>
    → Generic type for API responses.
  • data: T
    → Flexible data type.
  • function fetchData<T>(data: T)
    → Generic function.
  • return { ... }
    → Returns structured response.

Usage:

const user = fetchData({ name: "Fatima", city: "Lahore" });
const price = fetchData(5000); // PKR value

Example 2: Real-World Application — E-commerce Cart

type CartItem<T> = {
  product: T;
  quantity: number;
};

function calculateTotal<T extends { price: number }>(
  items: CartItem<T>[]
): number {
  return items.reduce((total, item) => {
    return total + item.product.price * item.quantity;
  }, 0);
}

Explanation:

  • T extends { price: number }
    → Ensures product has a price.
  • CartItem<T>[]
    → Array of cart items.
  • reduce(...)
    → Calculates total cost.

Usage:

const items = [
  { product: { name: "Laptop", price: 100000 }, quantity: 1 },
  { product: { name: "Mouse", price: 2000 }, quantity: 2 },
];

const total = calculateTotal(items); // PKR total


Common Mistakes & How to Avoid Them

Mistake 1: Overusing any Instead of Generics

❌ Wrong:

function identity(value: any): any {
  return value;
}

Problem:

  • Loses type safety.

✅ Fix:

function identity<T>(value: T): T {
  return value;
}

Explanation:

  • T preserves input type.
  • Ensures type safety.

Mistake 2: Missing Constraints

❌ Wrong:

function getPrice<T>(item: T) {
  return item.price;
}

Problem:

  • TypeScript error: price might not exist.

✅ Fix:

function getPrice<T extends { price: number }>(item: T) {
  return item.price;
}

Explanation:

  • Constraint ensures price exists.


Practice Exercises

Exercise 1: Extract Array Element Type

Problem:

Create a generic type that extracts the type inside an array.

Solution:

type ElementType<T> = T extends (infer U)[] ? U : T;

Explanation:

  • infer U extracts array element type.
  • Returns U if array, otherwise T.

Exercise 2: Create Readonly Version

Problem:

Convert all properties of a type into readonly.

Solution:

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

Explanation:

  • Iterates over keys.
  • Adds readonly modifier.

Frequently Asked Questions

What is TypeScript generics?

TypeScript generics allow you to write reusable and flexible code by defining types as variables. They help maintain type safety while working with different data types.

How do I use constraints in generics?

You can use the extends keyword to restrict generic types. This ensures that only types with specific properties or structures are allowed.

What is the infer keyword in TypeScript?

infer is used in conditional types to extract and assign a type dynamically. It is especially useful for working with complex types like function return values.

Why are generics important in real-world projects?

Generics improve code reusability, maintainability, and type safety. They are widely used in APIs, libraries, and frameworks like React.

How do distributive conditional types work?

They automatically apply conditions to each member of a union type. This allows more flexible and powerful type transformations.


Summary & Key Takeaways

  • Generics allow writing reusable and type-safe code
  • Constraints (extends) ensure safe property access
  • Conditional types enable dynamic type logic
  • infer helps extract types from complex structures
  • Mapped types transform object types efficiently
  • Advanced generics are essential for scalable applications

To continue your learning journey, explore these tutorials on theiqra.edu.pk:

  • Learn the basics in TypeScript Basics to strengthen your foundation
  • Dive deeper into Advanced TypeScript for real-world patterns
  • Explore backend development with Node.js API Development using TypeScript
  • Build scalable apps with React + TypeScript Complete Guide

Mastering advanced TypeScript generics will give you a strong edge in the Pakistani tech industry, whether you're aiming for jobs, internships, or freelancing on platforms like Fiverr and Upwork. Keep practicing and building real projects!

Practice the code examples from this tutorial
Open Compiler
Share this tutorial:

Test Your Python Knowledge!

Finished reading? Take a quick quiz to see how much you've learned from this tutorial.

Start Python Quiz

About Zaheer Ahmad