TypeScript course
typescript programming language icon

TypeScript Exercises: 20 Practice Problems with Solutions

TypeScript is learned by fighting the compiler and winning. These problems focus on the type system: annotations, interfaces, unions, generics, narrowing and the utility types you will use every day.

Run your answer in the editor below. Type errors show up in the output, which is exactly the feedback you want while practising types.

Run your answer here

Type your solution, press Run, and use the Input box when a problem asks you to read from standard input.

TypeScript Compiler
Output
Code runs on the Play with Coding execution engine — your code is saved locally for next time.

How to practise so it actually sticks

Attempt the problem before opening the solution, even if your first version is clumsy. A working ugly answer teaches more than a beautiful one you read. When you get stuck for more than five minutes, read the hint — not the solution — and try again.

After you pass, open the solution and ask what is different about it. Shorter? Fewer variables? A built-in you did not know? That comparison is where most of the growth happens. Then change the problem slightly: sort the other direction, handle an empty input, read a value instead of hard-coding it.

Aim for three to five problems a day rather than thirty in one sitting. Spacing practice over days is what moves syntax from "I can look it up" to "my fingers know it".

The exercises

Showing 20 of 20 exercises.

  1. BeginnerTypes & basics

    1. Annotate basic values

    Declare a string, a number and a boolean with explicit type annotations and log all three.

    Hint: const name: string = "Ada";

    Show solution
    const name: string = "Ada";
    const age: number = 36;
    const active: boolean = true;
    console.log(name, age, active);
  2. BeginnerTypes & basics

    2. Type a function

    Write add(a, b) with number parameters and a number return type.

    Hint: function add(a: number, b: number): number.

    Show solution
    function add(a: number, b: number): number {
      return a + b;
    }
    console.log(add(2, 3));
  3. BeginnerTypes & basics

    3. Typed arrays

    Create a string[] of three languages and log them uppercased.

    Hint: map((s) => s.toUpperCase()).

    Show solution
    const langs: string[] = ["ts", "go", "rust"];
    console.log(langs.map((s) => s.toUpperCase()));
  4. BeginnerInterfaces & types

    4. Describe an object with an interface

    Define interface User { id: number; name: string; email?: string } and create one user.

    Hint: The ? marks an optional property.

    Show solution
    interface User {
      id: number;
      name: string;
      email?: string;
    }
    const u: User = { id: 1, name: "Ada" };
    console.log(u);
  5. BeginnerInterfaces & types

    5. Union types

    Write format(v: string | number) that returns "n=5" for numbers and the string itself otherwise.

    Hint: Narrow with typeof v === "number".

    Show solution
    function format(v: string | number): string {
      return typeof v === "number" ? `n=${v}` : v;
    }
    console.log(format(5), format("hi"));
  6. IntermediateInterfaces & types

    6. Literal union as an enum

    Define type Status = "todo" | "doing" | "done" and a function returning a label for each.

    Hint: A switch over the union gets exhaustiveness checking.

    Show solution
    type Status = "todo" | "doing" | "done";
    function label(s: Status): string {
      switch (s) {
        case "todo": return "Not started";
        case "doing": return "In progress";
        case "done": return "Finished";
      }
    }
    console.log(label("doing"));
  7. IntermediateGenerics

    7. A generic function

    Write first<T>(items: T[]): T | undefined and use it on numbers and strings.

    Hint: The type parameter goes before the parentheses.

    Show solution
    function first<T>(items: T[]): T | undefined {
      return items[0];
    }
    console.log(first([1, 2, 3]), first(["a", "b"]));
  8. IntermediateGenerics

    8. Constrain a generic

    Write pluck<T, K extends keyof T>(rows: T[], key: K) returning an array of that field.

    Hint: keyof T gives the valid key names.

    Show solution
    function pluck<T, K extends keyof T>(rows: T[], key: K): T[K][] {
      return rows.map((r) => r[key]);
    }
    console.log(pluck([{ n: "a" }, { n: "b" }], "n"));
  9. IntermediateGenerics

    9. Partial, Pick and Omit

    From a Product type, build a PatchProduct with Partial and a ProductCard with Pick.

    Hint: Partial<T> makes every property optional.

    Show solution
    type Product = { id: number; title: string; price: number; stock: number };
    type PatchProduct = Partial<Product>;
    type ProductCard = Pick<Product, "title" | "price">;
    const patch: PatchProduct = { price: 9 };
    const card: ProductCard = { title: "Mug", price: 9 };
    console.log(patch, card);
  10. IntermediateGenerics

    10. Record for lookup tables

    Build a Record<string, number> of word lengths for three words.

    Hint: Record<K, V> describes an object with keys K and values V.

    Show solution
    const words = ["hi", "tree", "hello"];
    const lengths: Record<string, number> = {};
    for (const w of words) lengths[w] = w.length;
    console.log(lengths);
  11. AdvancedNarrowing

    11. Write a type guard

    Write isUser(v: unknown): v is User and use it to safely read a name from unknown data.

    Hint: Check typeof v === "object" && v !== null && "name" in v.

    Show solution
    type User = { name: string };
    function isUser(v: unknown): v is User {
      return typeof v === "object" && v !== null && "name" in v && typeof (v as User).name === "string";
    }
    const data: unknown = { name: "Ada" };
    console.log(isUser(data) ? data.name : "unknown");
  12. AdvancedNarrowing

    12. Discriminated union

    Model a Result as { ok: true; value: number } | { ok: false; error: string } and handle both.

    Hint: The shared literal field tells TypeScript which branch you are in.

    Show solution
    type Result = { ok: true; value: number } | { ok: false; error: string };
    function show(r: Result): string {
      return r.ok ? `value ${r.value}` : `failed: ${r.error}`;
    }
    console.log(show({ ok: true, value: 7 }), show({ ok: false, error: "nope" }));
  13. IntermediateNarrowing

    13. readonly and as const

    Create a frozen tuple of roles with as const and derive a union type from it.

    Hint: typeof roles[number] gives the element union.

    Show solution
    const roles = ["admin", "editor", "viewer"] as const;
    type Role = typeof roles[number];
    const r: Role = "editor";
    console.log(roles, r);
  14. IntermediateClasses

    14. A typed class

    Write class Stack<T> with push, pop and size, then use it with numbers.

    Hint: Keep a private items: T[].

    Show solution
    class Stack<T> {
      private items: T[] = [];
      push(item: T): void { this.items.push(item); }
      pop(): T | undefined { return this.items.pop(); }
      get size(): number { return this.items.length; }
    }
    const s = new Stack<number>();
    s.push(1); s.push(2);
    console.log(s.pop(), s.size);
  15. IntermediateClasses

    15. Implement an interface

    Define interface Shape { area(): number } and two classes implementing it.

    Hint: Use the implements keyword.

    Show solution
    interface Shape { area(): number }
    class Square implements Shape {
      constructor(private side: number) {}
      area() { return this.side ** 2; }
    }
    class Circle implements Shape {
      constructor(private r: number) {}
      area() { return Math.PI * this.r ** 2; }
    }
    console.log(new Square(3).area(), new Circle(1).area().toFixed(2));
  16. AdvancedAsync

    16. Typed async function

    Write loadUser(id: number): Promise<User> that resolves a fake user after a delay.

    Hint: An async function returns Promise<T>.

    Show solution
    type User = { id: number; name: string };
    const delay = (ms: number) => new Promise<void>((res) => setTimeout(res, ms));
    async function loadUser(id: number): Promise<User> {
      await delay(50);
      return { id, name: "Ada" };
    }
    loadUser(1).then((u) => console.log(u));
  17. AdvancedAsync

    17. Catch clause is unknown

    Catch an error and log its message safely, given catch variables are typed unknown.

    Hint: Narrow with err instanceof Error.

    Show solution
    function risky(): never {
      throw new Error("boom");
    }
    try {
      risky();
    } catch (err: unknown) {
      console.log(err instanceof Error ? err.message : String(err));
    }
  18. AdvancedAdvanced types

    18. Write a mapped type

    Create Nullable<T> that makes every property T[K] | null.

    Hint: { [K in keyof T]: T[K] | null }.

    Show solution
    type Nullable<T> = { [K in keyof T]: T[K] | null };
    type Row = { id: number; title: string };
    const r: Nullable<Row> = { id: 1, title: null };
    console.log(r);
  19. AdvancedAdvanced types

    19. Generic group-by

    Write groupBy<T>(rows: T[], key: (row: T) => string): Record<string, T[]>.

    Hint: Build the object with reduce, creating arrays lazily.

    Show solution
    function groupBy<T>(rows: T[], key: (row: T) => string): Record<string, T[]> {
      return rows.reduce<Record<string, T[]>>((acc, row) => {
        const k = key(row);
        (acc[k] ||= []).push(row);
        return acc;
      }, {});
    }
    console.log(groupBy(["hi", "cat", "tree"], (w) => String(w.length)));
  20. AdvancedAdvanced types

    20. Exhaustiveness with never

    Add a default branch that assigns the value to never so new union members become compile errors.

    Hint: const _exhaustive: never = value;

    Show solution
    type Shape = { kind: "square"; side: number } | { kind: "circle"; r: number };
    function area(s: Shape): number {
      switch (s.kind) {
        case "square": return s.side ** 2;
        case "circle": return Math.PI * s.r ** 2;
        default: {
          const _exhaustive: never = s;
          return _exhaustive;
        }
      }
    }
    console.log(area({ kind: "square", side: 4 }));

What to do next

If a whole topic feels shaky, go back to that chapter in the 17-chapter TypeScript course and re-read it, then return here. When the advanced problems feel routine, take the final TypeScript quiz and claim your certificate, or open the TypeScript online compiler and build something of your own.