TypeScript Tutorial menu

TypeScript Generics and Utility Types

Generics let you write code that works with many types, while still keeping full type safety.

The Problem

Suppose you want a function that returns the first item of any array. With any, you lose the type:

Example

typescript
function firstAny(list: any[]): any {
  return list[0];
}

const x = firstAny(["Karachi", "Lahore"]);  // x is any: no help, no checks

A Generic Function

A generic function has a type parameter, written in angle brackets, often called T. It is like a variable for a type.

Example

typescript
function first<T>(list: T[]): T | undefined {
  return list[0];
}

const city = first(["Karachi", "Lahore"]);   // city: string | undefined
const mark = first([88, 92, 75]);            // mark: number | undefined

console.log(city, mark);
Output
Karachi 88

TypeScript works out T from the argument you pass. You can also write it yourself: first<string>(["a", "b"]).

More Than One Type Parameter

Example

typescript
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

const p = pair("rollNo", 117);   // [string, number]
console.log(p);
Output
[ 'rollNo', 117 ]

Generic Interfaces

Generics are very common for describing data that wraps other data, like an API response:

Example

typescript
interface ApiResponse<T> {
  ok: boolean;
  data: T;
}

interface Course {
  title: string;
  months: number;
}

const res: ApiResponse<Course[]> = {
  ok: true,
  data: [
    { title: "Web Development", months: 4 },
    { title: "Graphic Designing", months: 3 }
  ]
};

console.log(res.data.map(c => c.title).join(", "));
Output
Web Development, Graphic Designing

The same ApiResponse works for users, orders or anything else.

Constraints With extends

Sometimes T must have certain properties. T extends ... adds a constraint:

Example

typescript
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(longest("Hyderabad", "Sukkur"));
console.log(longest([1, 2], [1, 2, 3]));
// longest(10, 20);  // Error: number does not have a 'length' property
Output
Hyderabad
[ 1, 2, 3 ]

A Generic Class

Example

typescript
class Stack<T> {
  private items: T[] = [];

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

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

  size(): number {
    return this.items.length;
  }
}

const plates = new Stack<string>();
plates.push("Red plate");
plates.push("Blue plate");
console.log(plates.pop(), plates.size());
Output
Blue plate 1

Utility Types

TypeScript comes with ready-made generic types that change other types:

Utility typeResult
Partial<T>All properties of T become optional
Required<T>All properties become required
Readonly<T>All properties become readonly
Pick<T, "a" OR "b">Only the listed properties
Omit<T, "a">All properties except the listed ones
Record<K, V>An object with keys K and values V

(In real code, the keys in Pick and Omit are joined with the | union symbol.)

Example

typescript
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type UserUpdate = Partial<User>;
type PublicUser = Omit<User, "password">;
type UserCard = Pick<User, "name" | "email">;

const update: UserUpdate = { name: "Ayesha Khan" };
const card: UserCard = { name: "Ayesha Khan", email: "ayesha@example.com" };
const fees: Record<string, number> = { html: 3000, javascript: 5000 };

console.log(update, card.email, fees.javascript);
Output
{ name: 'Ayesha Khan' } ayesha@example.com 5000

Name type parameters clearly when there are several: TKey and TValue are easier to follow than K and V in long code.

Exercise

Exercise

What is the return type of first(["a", "b"]) if function first<T>(list: T[]): T?

Learn TypeScript with a teacher

Join the Full Stack Development course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp