Skip to content
Laviyx Learn

Try

TypeScript Generics and Utility Types Practice

Practise generic functions and classes, constraints, keyof, utility types and mapped types. Every solution has been type-checked in strict mode and run, and shows its real output.

Advanced6 problemsUpdated

Review first: Generics, Generic Constraints and Defaults, keyof, typeof and Indexed Access, Utility Types, Mapped Types

  1. 1.A generic function

    Intermediate

    Write last<T>(items: readonly T[]): T | undefined and show that it returns a number for a number array and a string for a string array.

    Show hint

    items.at(-1) returns the last item or undefined.

    Show solution
    TypeScript
    function last<T>(items: readonly T[]): T | undefined {
      return items.at(-1);
    }
    const n = last([1, 2, 3]);
    const s = last(["html", "css"]);
    console.log((n ?? 0) * 10, s?.toUpperCase());
    Output
    30 CSS
  2. 2.A generic class

    Intermediate

    Write a Queue<T> class with enqueue(item: T), dequeue(): T | undefined and a size getter. Use a Queue<string> for three customers and serve the first one.

    Show hint

    Store the items in a private T[]; shift() removes the first item.

    Show solution
    TypeScript
    class Queue<T> {
      #items: T[] = [];
    
      enqueue(item: T): void {
        this.#items.push(item);
      }
    
      dequeue(): T | undefined {
        return this.#items.shift();
      }
    
      get size(): number {
        return this.#items.length;
      }
    }
    
    const line = new Queue<string>();
    line.enqueue("Asha");
    line.enqueue("Ravi");
    line.enqueue("Meera");
    console.log(line.dequeue(), line.size);
    Output
    Asha 2
  3. 3.Type-safe property access

    Intermediate

    Write pluck<T, K extends keyof T>(items: T[], key: K): T[K][] and use it to get the names and the prices from a product list.

    Show hint

    items.map((item) => item[key]).

    Show solution
    TypeScript
    function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
      return items.map((item) => item[key]);
    }
    
    const products = [
      { name: "Pen", price: 20 },
      { name: "Bag", price: 1500 },
    ];
    console.log(pluck(products, "name"), pluck(products, "price"));
    Output
    [ 'Pen', 'Bag' ] [ 20, 1500 ]
  4. 4.Partial updates with Omit

    Intermediate

    Given interface User { id: number; name: string; email: string; role: "admin" | "member" }, write update(user: User, changes: Partial<Omit<User, "id">>): User so the id can never be changed, and test it.

    Show hint

    Spread the user, then the changes.

    Show solution
    TypeScript
    interface User {
      id: number;
      name: string;
      email: string;
      role: "admin" | "member";
    }
    
    function update(user: User, changes: Partial<Omit<User, "id">>): User {
      return { ...user, ...changes };
    }
    
    const asha: User = { id: 1, name: "Asha", email: "asha@example.com", role: "member" };
    console.log(update(asha, { role: "admin" }));
    Output
    { id: 1, name: 'Asha', email: 'asha@example.com', role: 'admin' }
  5. 5.A generic groupBy

    Advanced

    Write groupBy<T, K extends string>(items: T[], getKey: (item: T) => K): Partial<Record<K, T[]>> and group a list of tasks by their status.

    Show hint

    Start with an empty object and push each item into the array for its key, creating the array with ??=.

    Show solution
    TypeScript
    function groupBy<T, K extends string>(items: T[], getKey: (item: T) => K): Partial<Record<K, T[]>> {
      const groups: Partial<Record<K, T[]>> = {};
      for (const item of items) {
        (groups[getKey(item)] ??= []).push(item);
      }
      return groups;
    }
    
    type Task = { title: string; status: "todo" | "done" };
    const tasks: Task[] = [
      { title: "Learn generics", status: "done" },
      { title: "Practice", status: "todo" },
      { title: "Read utility types", status: "done" },
    ];
    
    const byStatus = groupBy(tasks, (t) => t.status);
    console.log(byStatus.done?.map((t) => t.title), byStatus.todo?.length);
    Output
    [ 'Learn generics', 'Read utility types' ] 1
  6. 6.A mapped type

    Advanced

    Write a mapped type Validation<T> that maps every property of T to string | null (an error message or null), and use it to validate a sign-up object where the email must contain "@" and the age must be at least 18.

    Show hint

    type Validation<T> = { [K in keyof T]: string | null }.

    Show solution
    TypeScript
    type Validation<T> = { [K in keyof T]: string | null };
    
    interface Signup {
      email: string;
      age: number;
    }
    
    function validate(s: Signup): Validation<Signup> {
      return {
        email: s.email.includes("@") ? null : "Email is not valid",
        age: s.age >= 18 ? null : "Must be 18 or older",
      };
    }
    
    console.log(validate({ email: "asha.example.com", age: 21 }));
    Output
    { email: 'Email is not valid', age: null }