FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

TypeScript Generics Explained: A Practical Guide with Real Examples

Master TypeScript generics from basic syntax to advanced patterns. Covers generic functions, interfaces, constraints, utility types, and common interview questions.

Yusuf SeyitoğluMarch 11, 20262 views10 min read

TypeScript Generics Explained: A Practical Guide with Real Examples

Generics are one of the most powerful features in TypeScript β€” and one of the most avoided by developers who find them intimidating. Once you understand them, you will use them constantly. They are the difference between type-safe reusable code and copy-pasted functions with slightly different types.

This guide builds your understanding from first principles to advanced patterns.

Why Generics Exist

Imagine you need a function that returns the first element of an array. Without generics:

typescript
function firstString(arr: string[]): string { return arr[0]; } function firstNumber(arr: number[]): number { return arr[0]; }

That is repetition. You could use any:

typescript
function first(arr: any[]): any { return arr[0]; }

But now you have lost all type information. If you call first(["hello", "world"]), TypeScript has no idea the return type is string.

Generics solve both problems β€” one function, full type safety:

typescript
function first<T>(arr: T[]): T { return arr[0]; } const s = first(["hello", "world"]); // inferred: string const n = first([1, 2, 3]); // inferred: number

T is a type parameter β€” a placeholder TypeScript replaces with the actual type at the call site.

Basic Syntax

The angle brackets <T> declare a type parameter. You can use any name, but single letters like T, K, V are convention:

typescript
function identity<T>(value: T): T { return value; } function pair<A, B>(a: A, b: B): [A, B] { return [a, b]; } const result = pair("hello", 42); // [string, number]

TypeScript infers the type arguments from what you pass in. You can also be explicit:

typescript
const result = pair<string, number>("hello", 42);

Generic Interfaces and Types

Generics are not just for functions β€” they work on interfaces and type aliases too.

typescript
interface ApiResponse<T> { data: T; status: number; message: string; } interface User { id: number; name: string; } const response: ApiResponse<User> = { data: { id: 1, name: "Alice" }, status: 200, message: "OK", }; const listResponse: ApiResponse<User[]> = { data: [{ id: 1, name: "Alice" }], status: 200, message: "OK", };

This pattern is everywhere in real codebases β€” API response wrappers, paginated results, event payloads.

Generic Classes

typescript
class Stack<T> { 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<number>(); numberStack.push(1); numberStack.push(2); numberStack.pop(); // 2 const stringStack = new Stack<string>(); stringStack.push("hello");

Generic Constraints

Sometimes you need to restrict what types T can be. Use extends to add constraints:

typescript
function getLength<T extends { length: number }>(value: T): number { return value.length; } getLength("hello"); // 5 getLength([1, 2, 3]); // 3 getLength({ length: 10, name: "file" }); // 10 getLength(42); // Error: number has no length property

Constraining to object keys

A very common pattern β€” ensuring a key actually exists on an object:

typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "Alice", age: 30 }; getProperty(user, "name"); // string getProperty(user, "age"); // number getProperty(user, "role"); // Error: "role" not in keyof user

keyof T produces a union of all keys of T. K extends keyof T means K must be one of those keys.

Default Type Parameters

Like default function parameters, type parameters can have defaults:

typescript
interface Pagination<T = unknown> { items: T[]; page: number; total: number; } const untyped: Pagination = { items: [], page: 1, total: 0 }; const typed: Pagination<User> = { items: [], page: 1, total: 0 };

Built-in Utility Types

TypeScript ships with generic utility types that you will use constantly:

Partial and Required

typescript
interface User { id: number; name: string; email: string; } type PartialUser = Partial<User>; -- All fields become optional -- { id?: number; name?: string; email?: string } type RequiredUser = Required<Partial<User>>; -- All fields become required again

Pick and Omit

typescript
type UserPreview = Pick<User, "id" | "name">; -- { id: number; name: string } type UserWithoutId = Omit<User, "id">; -- { name: string; email: string }

Record

typescript
type RolePermissions = Record<string, boolean>; const permissions: RolePermissions = { canRead: true, canWrite: false, canDelete: false, }; type Status = "pending" | "active" | "inactive"; type StatusLabels = Record<Status, string>; const labels: StatusLabels = { pending: "Awaiting approval", active: "Active account", inactive: "Account disabled", };

ReturnType and Parameters

typescript
function fetchUser(id: number, includeProfile: boolean) { return { id, name: "Alice" }; } type FetchUserReturn = ReturnType<typeof fetchUser>; -- { id: number; name: string } type FetchUserParams = Parameters<typeof fetchUser>; -- [id: number, includeProfile: boolean]

Conditional Types

Advanced generics can branch based on type conditions:

typescript
type IsArray<T> = T extends any[] ? true : false; type A = IsArray<string[]>; -- true type B = IsArray<string>; -- false -- Extract the element type from an array type ElementType<T> = T extends (infer U)[] ? U : never; type StrElement = ElementType<string[]>; -- string type NumElement = ElementType<number[]>; -- number

Conditional types with infer look complex but appear in many library types. The pattern T extends SomeType<infer U> ? U : never means "if T matches this shape, extract U from it."

Real-World Example: A Typed fetch Wrapper

Generics shine when building utility functions used across a codebase:

typescript
async function apiFetch<T>(url: string): Promise<T> { const res = await fetch(url); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${url}`); } return res.json() as Promise<T>; } interface Product { id: number; name: string; price: number; } const product = await apiFetch<Product>("/api/products/1"); -- product is fully typed as Product

Common Interview Questions

Q: What is the difference between any and generics?

any disables type checking entirely. Generics preserve type information β€” TypeScript tracks the actual type and enforces it throughout. Use generics when you want flexibility without sacrificing safety.

Q: What does T extends keyof U mean?

It constrains T to be one of the keys of type U. This ensures you cannot pass a key that does not exist on the object.

Q: What is the difference between interface Foo<T> and type Foo<T>?

Both support generics. interface supports declaration merging and is slightly preferred for public API shapes. type is more flexible β€” it can represent unions, intersections, and mapped types. In practice, either works for most generic shapes.

Practice TypeScript on Froquiz

TypeScript is increasingly required in frontend and backend roles. Test your skills on Froquiz across a wide range of JavaScript and TypeScript topics.

Summary

  • Generics use type parameters (<T>) to write reusable, type-safe code
  • Use extends to constrain what types are allowed
  • keyof T and K extends keyof T enable type-safe property access
  • Built-in utility types (Partial, Pick, Omit, Record, ReturnType) use generics under the hood
  • Conditional types with infer let you extract types programmatically
  • Prefer generics over any whenever you need flexibility without losing type safety

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
  • Java Collections Deep Dive: ArrayList, HashMap, TreeMap, LinkedHashMap and When to Use EachMar 12
All Blogs