TypeScript: TypeScript Generics Basics

Last updated: 2026-08-26

Generics are TypeScript’s most powerful feature—they allow you to write code that “works with multiple types” while maintaining full type safety. Understanding generics is a key step in mastering TypeScript.

1. Why Are Generics Needed?

(1) The Dilemma of Not Having Generics

TYPESCRIPT
// Option 1: Use any — missing type information
function firstAny(arr: any[]): any {
  return arr[0];
}

let n = firstAny([1, 2, 3]);    // n is any — don't know if it's number
let s = firstAny(["a", "b"]);   // s is any — don't know if it's string

// Option 2:Write a function for each type——Code Duplication
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }
// Endless...

(2) Generic Solutions

TYPESCRIPT
function first<T>(arr: T[]): T {
  return arr[0];
}

let n = first([1, 2, 3]);       // T Inferred as number → Back number
let s = first(["a", "b"]);      // T Inferred as string → Back string
let b = first([true, false]);   // T Inferred as boolean → Back boolean

console.log(n.toFixed(2));      // ✅ TypeScript knows n is number
console.log(s.toUpperCase());   // ✅ TypeScript knows s is string
📌 Key Concept: <T> is a type parameter—what T is depends on the type of the argument passed when the function is called. It is defined once but can be used with multiple types, and precise type information is available for each case.



2. Generic Functions

(1) Basic Syntax

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

// TypeScript Automatic Inference T
let num = identity(42);       // T = number
let str = identity("hello");  // T = string

// You can also specify it explicitly T(Sometimes it is necessary to)
let result = identity<string>("hello");

(2) Multiple type parameters

TYPESCRIPT
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

let p1 = pair("name", 42);        // [string, number]
let p2 = pair(true, [1, 2, 3]);   // [boolean, number[]]

(3) Generics and Arrays

TYPESCRIPT
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
  return arr.map(transform);
}

let numbers = [1, 2, 3];
let strings = map(numbers, n => `No.${n}`);
// T = number, U = string → Back string[]

console.log(strings);  // ["No.1", "No.2", "No.3"]

▶ Example: Generic Utility Functions

TYPESCRIPT
// Filter Array
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

// Searching an Array
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
  return arr.find(predicate);
}

// Array Chunking
function chunk<T>(arr: T[], size: number): T[][] {
  let result: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

let nums = [1, 2, 3, 4, 5, 6, 7];
let evens = filter(nums, n => n % 2 === 0);
let found = find(nums, n => n > 5);
let groups = chunk(nums, 3);

console.log("Even number:" + evens);        // "Even number:2,4,6"
console.log("Found:" + found);        // "Found:6"
console.log("Chunking:" + JSON.stringify(groups));  // "[[1,2,3],[4,5,6],[7]]"
▶ Try it Yourself

Output:

TEXT 📖 Display only
Even number:2,4,6
Found:6
Chunking:[[1,2,3],[4,5,6],[7]]


3. Generic Interfaces

(1) Basic Syntax

TYPESCRIPT
interface Box<T> {
  value: T;
}

let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };

(2) Generic Interface Description Function

TYPESCRIPT
interface Transformer<A, B> {
  (input: A): B;
}

let toString: Transformer<number, string> = (n) => String(n);
let toLength: Transformer<string, number> = (s) => s.length;

console.log(toString(42));       // "42"
console.log(toLength("hello"));  // 5

(3) The Generic Interface Description Repository Pattern

TYPESCRIPT
interface Repository<T> {
  findById(id: string): T | null;
  findAll(): T[];
  save(item: T): void;
  delete(id: string): boolean;
}

interface User {
  id: string;
  name: string;
}

class UserRepo implements Repository<User> {
  private data: User[] = [];

  findById(id: string): User | null {
    return this.data.find(u => u.id === id) ?? null;
  }

  findAll(): User[] {
    return this.data;
  }

  save(item: User): void {
    this.data.push(item);
  }

  delete(id: string): boolean {
    let len = this.data.length;
    this.data = this.data.filter(u => u.id !== id);
    return this.data.length < len;
  }
}


4. Generic Classes

(1) Basic Syntax

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;
  }
}

let numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push(3);
console.log(numStack.pop());   // 3
console.log(numStack.peek());  // 2
console.log(numStack.size);    // 2

let strStack = new Stack<string>();
strStack.push("a");
strStack.push("b");
console.log(strStack.pop());   // "b"

(2) Static Members of Generic Classes

TYPESCRIPT
class GenericClass<T> {
  // static defaultValue: T;  // ❌ Static members cannot reference a class's type parameters

  // ✅ Static methods require their own type parameters.
  static create<U>(value: U): GenericClass<U> {
    let instance = new GenericClass<U>(value);
    return instance;
  }

  constructor(public value: T) {}
}

let instance = GenericClass.create("hello");  // GenericClass<string>
📌 Reason: The class's type parameter T is instance-level—each instance can have a different T. Static members belong to the class itself, not to any specific instance, so they cannot reference an instance's type parameters.



5. Generic Constraints (extends)

By default, the generic type T can be any type—sometimes this is too broad. Use extends to constrain T so that it must satisfy certain conditions:

(1) Constrained to a specific interface

TYPESCRIPT
interface HasLength {
  length: number;
}

// T Must have length Properties
function logLength<T extends HasLength>(value: T): void {
  console.log(`Length:${value.length}`);
}

logLength("hello");        // ✅ string has length
logLength([1, 2, 3]);      // ✅ The array contains length
logLength({ length: 10 }); // ✅ Objects include length
// logLength(42);          // ❌ number None length

(2) A constraint is another generic

TYPESCRIPT
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let user = { name: "Charlie", age: 20, email: "xiao@example.com" };

console.log(getProperty(user, "name"));   // ✅ "Charlie" —— K = "name"
console.log(getProperty(user, "age"));    // ✅ 20 —— K = "age"
// getProperty(user, "phone");            // ❌ "phone" No user the key

(3) Constraints as Constructors

TYPESCRIPT
function createInstance<T>(Constructor: new () => T): T {
  return new Constructor();
}

class Dog {
  bark() { return "Woof!"; }
}

let dog = createInstance(Dog);
console.log(dog.bark());  // "Woof!"

▶ Example: Generic Function with keyof Constraint

TYPESCRIPT
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  let result = {} as Pick<T, K>;
  for (let key of keys) {
    result[key] = obj[key];
  }
  return result;
}

let user = { id: 1, name: "Charlie", email: "c@example.com", age: 25 };

let summary = pick(user, "id", "name");
// Type: { id: number; name: string }
console.log(summary.name);  // "Charlie"
// summary.email;           // ❌ email was not picked
▶ Try it Yourself

Output:

TEXT 📖 Display only
Charlie

▶ Example: Type-Safe Object Merging

TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

let defaults = { host: "localhost", port: 3000 };
let custom = { port: 8080, debug: true };

let config = merge(defaults, custom);
// Inference Types:{ host: string; port: number } & { port: number; debug: boolean }
// After simplification:{ host: string; port: number; debug: boolean }

console.log(config.host);   // "localhost"
console.log(config.port);   // 8080
console.log(config.debug);  // true
▶ Try it Yourself

Output:

TEXT 📖 Display only
localhost
8080
true


6. Common Built-in Generic Types

TypeScript includes many useful generic utility types:

Type Function Example
Array<T> Array Type Array<number>
Promise<T> Promise Type Promise&lt;string&gt;
Record<K, V> Key-value pair object Record<string, number>
Partial<T> All attributes are optional Partial<Config>
Required<T> All fields are required Required<Config>
Readonly<T> All properties are read-only Readonly<Config>
Pick<T, K> Select certain attributes Pick<User, "name" | "age">
Omit<T, K> Exclude certain attributes Omit<User, "email">
Exclude<T, U> Exclude from Union Types Exclude<"a"|"b"|"c", "a">
Extract<T, U> Extract from Union Types Extract<"a"|"b"|"c", "a"|"b">
ReturnType<T> Function Return Type ReturnType<typeof fn>
Parameters<T> Tuple of Function Parameter Types Parameters<typeof fn>
TYPESCRIPT
// Examples of Actual Use
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// When creating a user id Not required
type CreateUser = Omit<User, "id">;

// All fields are optional when updating a user
type UpdateUser = Partial<User>;

// The user summary includes only some fields
type UserSummary = Pick<User, "id" | "name">;

let newUser: CreateUser = { name: "Charlie", email: "xiao@example.com", age: 20 };
let update: UpdateUser = { age: 21 };
let summary: UserSummary = { id: 1, name: "Charlie" };

❓ FAQ

Q How should generic type parameters be named?
A By convention, use a single uppercase letter—T (Type), U/V/W (subsequent types), K (Key), V (Value), E (Element). For multiple words, use PascalCase: TItem, TResponse. Teams should adopt a consistent naming convention.
Q When do you need to explicitly specify a generic type?
A In most cases, TypeScript can infer generic types from the parameters. You need to specify them explicitly in the following scenarios: (1) The function has no parameters, or the parameter types do not allow T to be determined; (2) The inference is too broad (e.g., inferred as a union type); (3) You need more precise control over the function’s return type.
Q What is the difference between the generic constraint extends and class inheritance extends?
A The syntax is the same, but the semantics differ. The generic constraint T extends HasLength means “T must have at least the structure of HasLength”—it is a type compatibility relationship. Class inheritance class Dog extends Animal means “Dog is a subclass of Animal”—it is an object-oriented relationship.
Q What is the difference between Record<string, number> and { [key: string]: number }?
A They are completely equivalent. Record is a built-in generic utility type that is essentially an index signature. Record<string, number> is more concise, while { [key: string]: number } is more flexible (it allows you to add known properties).

📖 Summary

📝 Exercises

  1. Basic Problem (Difficulty ⭐): Write a generic function wrapInArray<T>(value: T): T[] that wraps a single value into an array. Test it with number and string types.
  2. Advanced Problem (Difficulty ⭐⭐): Write a generic function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] that extracts the value of a specified property from an array of objects. For example, pluck([{name: "a", age: 1}, {name: "b", age: 2}], "name") returns ["a", "b"].
  3. Challenge (Difficulty: ⭐⭐⭐): Implement a generic class DataStore<T extends { id: string }> that provides save, find, and delete methods. Ensure that the id property exists through generic constraints. Create an instance of DataStore<{ id: string; title: string }> to test the CRUD functionality.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏