100 TypeScript practice problems with solutions

Looking to go from understanding TypeScript basics to comfortably writing type-safe code? This post gives you 100 TypeScript practice problems with clear, step-by-step solutions. You’ll work on simple type annotations, interfaces, and generics, then move into union types, mapped types, and using TypeScript with functions and classes.

Each problem is written in plain language—try it yourself first, then check the solution and truly understand the why. Perfect for building real confidence, whether you’re just starting out or prepping for a front-end interview. Pick a problem, start coding, and see how much stronger your TypeScript skills can get.

Also try it: 100 Javascript practice problems with solutions

Basic Types & Type Annotations

1. Hello World with Types
Write a function greet(name: string): string that returns “Hello, ” + name.

ts

function greet(name: string): string {
  return "Hello, " + name;
}

2. Sum of Two Numbers
Write a typed function sum(a: number, b: number): number.

ts

function sum(a: number, b: number): number {
  return a + b;
}

3. Even Check
Function isEven(num: number): boolean.

ts

function isEven(num: number): boolean {
  return num % 2 === 0;
}

4. Maximum of Three Numbers

ts

function maxOfThree(a: number, b: number, c: number): number {
  return Math.max(a, b, c);
}

5. Reverse a String

ts

function reverseString(str: string): string {
  return str.split('').reverse().join('');
}

6. Factorial (Recursive)

ts

function factorial(n: number): number {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

7. Palindrome Check
Case-insensitive, ignoring non-alphanumeric characters.

ts

function isPalindrome(str: string): boolean {
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  return cleaned === cleaned.split('').reverse().join('');
}

8. Longest Word

ts

function longestWord(sentence: string): string {
  return sentence.split(' ').reduce((longest, current) =>
    current.length > longest.length ? current : longest, '');
}

9. Capitalize Words

ts

function capitalizeWords(str: string): string {
  return str.replace(/\b\w/g, char => char.toUpperCase());
}

10. Count Vowels

ts

function countVowels(str: string): number {
  const matches = str.match(/[aeiou]/gi);
  return matches ? matches.length : 0;
}

Functions

11. Optional Parameters
concatStrings(a: string, b?: string, separator: string = ' ') returns a + separator + b if b is provided.

ts

function concatStrings(a: string, b?: string, separator: string = ' '): string {
  return b ? a + separator + b : a;
}

12. Function Overloads
Define add that can take two numbers or two strings and returns corresponding type.

ts

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

13. Rest Parameters
sumAll(...nums: number[]): number.

ts

function sumAll(...nums: number[]): number {
  return nums.reduce((total, num) => total + num, 0);
}

14. Generic Function (First Element)

ts

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

15. Generic with Constraint (getProperty)

ts

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

16. Arrow Function Types

ts

const multiply = (a: number, b: number): number => a * b;

17. Union Parameter
formatDate(date: string | Date, format: string = 'YYYY-MM-DD'): string.

ts

function formatDate(date: string | Date, format: string = 'YYYY-MM-DD'): string {
  const d = typeof date === 'string' ? new Date(date) : date;
  return d.toISOString().split('T')[0];
}

18. Typing this

ts

interface User {
  name: string;
  age: number;
}
function getUserInfo(this: User): string {
  return `${this.name}, ${this.age} years old`;
}
// Usage: getUserInfo.call({ name: 'Alice', age: 30 })

19. Callback Typing

ts

function processArray(arr: number[], callback: (item: number, index: number) => void): void {
  arr.forEach((item, index) => callback(item, index));
}

20. Higher-Order Function

ts

function createAdder(a: number): (b: number) => number {
  return (b: number) => a + b;
}

Interfaces & Type Aliases

21. Interface and Method

ts

interface Person {
  name: string;
  age: number;
  email?: string;
}
function introduce(person: Person): string {
  return `Hi, I'm ${person.name}, ${person.age} years old.`;
}

22. Extending Interface

ts

interface Employee extends Person {
  employeeId: number;
  department: string;
}

23. Union Type Alias

ts

type Status = "active" | "inactive" | "suspended";
function checkStatus(status: Status): boolean {
  return status === "active";
}

24. Intersection Type

ts

type Admin = { adminLevel: number };
type AdminUser = Person & Admin;
const admin: AdminUser = { name: 'Bob', age: 40, adminLevel: 1 };

25. Readonly Interface

ts

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}
const config: Config = { apiUrl: 'https://api.example.com', timeout: 5000 };
// config.apiUrl = ''; // Error

26. Function in Interface

ts

interface Calculator {
  add(x: number, y: number): number;
  subtract(x: number, y: number): number;
}

27. Implementing Interface with Class

ts

class MyCalculator implements Calculator {
  add(x: number, y: number): number { return x + y; }
  subtract(x: number, y: number): number { return x - y; }
}

28. Index Signature

ts

interface StringDictionary {
  [key: string]: string;
}
const dict: StringDictionary = { name: 'Alice', city: 'NY' };

29. Hybrid Type (Callable + Property)

ts

interface Counter {
  (start: number): string;
  reset(): void;
}
function createCounter(): Counter {
  let count = 0;
  const counter = (start: number) => `Count: ${(count = start)}`;
  counter.reset = () => { count = 0; };
  return counter;
}

30. typeof to Derive Type

ts

const defaultConfig = { endpoint: 'https://api.com', retries: 3 };
type ConfigType = typeof defaultConfig;

Classes

31. Access Modifiers

ts

class Car {
  private speed: number = 0;
  protected model: string;
  public brand: string;
  constructor(brand: string, model: string) {
    this.brand = brand;
    this.model = model;
  }
  accelerate(inc: number): void { this.speed += inc; }
  getSpeed(): number { return this.speed; }
}

32. Abstract Class

ts

abstract class Shape {
  abstract getArea(): number;
  display(): void { console.log(`Area: ${this.getArea()}`); }
}
class Circle extends Shape {
  constructor(private radius: number) { super(); }
  getArea(): number { return Math.PI * this.radius ** 2; }
}

33. Static Members

ts

class MathUtil {
  static readonly PI = 3.14159;
  static square(x: number): number { return x * x; }
}

34. Readonly Property

ts

class Book {
  readonly isbn: string;
  title: string;
  constructor(isbn: string, title: string) {
    this.isbn = isbn;
    this.title = title;
  }
}

35. Parameter Properties

ts

class EmployeeC {
  constructor(public name: string, private salary: number, protected department: string) {}
  getSalary(): number { return this.salary; }
}

36. Getters / Setters

ts

class Temperature {
  constructor(private _celsius: number) {}
  get celsius(): number { return this._celsius; }
  set celsius(val: number) { this._celsius = val; }
  get fahrenheit(): number { return this._celsius * 9/5 + 32; }
  set fahrenheit(val: number) { this._celsius = (val - 32) * 5/9; }
}

37. Multiple Interface Implementation

ts

interface Logger { log(msg: string): void; }
interface Formatter { format(obj: any): string; }
class ConsoleLogger implements Logger, Formatter {
  log(msg: string): void { console.log(msg); }
  format(obj: any): string { return JSON.stringify(obj); }
}

38. Class with Index Signature

ts

class ConfigStore {
  [key: string]: number;
  get(key: string): number | undefined { return this[key]; }
}

39. Fluent API with this Return

ts

class Builder {
  private name: string = '';
  private age: number = 0;
  setName(name: string): this { this.name = name; return this; }
  setAge(age: number): this { this.age = age; return this; }
  build(): { name: string; age: number } { return { name: this.name, age: this.age }; }
}

40. Singleton Pattern

ts

class Database {
  private static instance: Database;
  private constructor() {}
  static getInstance(): Database {
    if (!Database.instance) Database.instance = new Database();
    return Database.instance;
  }
  query(sql: string): void { console.log(sql); }
}

Generics

41. Generic Reverse Array

ts

function reverseArray<T>(arr: T[]): T[] {
  return arr.slice().reverse();
}

42. Generic Interface

ts

interface Repository<T> {
  getAll(): T[];
  getById(id: number): T | undefined;
}

43. Generic Class (Stack)

ts

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

44. Generic Constraint

ts

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

45. Multiple Type Parameters

ts

function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

46. Default Type Parameter

ts

class Container<T = string> {
  constructor(public value: T) {}
}
const defaultString = new Container('Hello');
const numberContainer = new Container<number>(42);

47. Implement MyPartial

ts

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

48. Conditional Type

ts

type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'

49. keyof and Lookup

ts

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

50. Mapped Type ReadonlyOptional

ts

type ReadonlyOptional<T> = {
  readonly [K in keyof T]?: T[K];
};

51. PickByType

ts

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Example { name: string; age: number; }
type OnlyNumber = PickByType<Example, number>; // { age: number }

Also try it: 100 kotlin practice problems with solutions

52. OmitByType

ts

type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K]: T[K];
};
type NoNumber = OmitByType<Example, number>; // { name: string }

53. Implement MyReturnType

ts

type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never;
type Fn = (a: number) => string;
type R = MyReturnType<Fn>; // string

54. Implement MyParameters

ts

type MyParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type P = MyParameters<(x: number, y: string) => void>; // [number, string]

55. Implement MyConstructorParameters

ts

type MyConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
class PersonC7 { constructor(name: string, age: number) {} }
type PersonParams = MyConstructorParameters<typeof PersonC7>; // [string, number]

Enums

56. Numeric Enum

ts

enum Direction { Up = 1, Down, Left, Right }

57. String Enum

ts

enum LogLevel { Error = 'ERROR', Warn = 'WARN', Info = 'INFO' }

58. Const Enum

ts

const enum StatusCodes { OK = 200, NotFound = 404 }

59. Computed Enum Members

ts

enum FileAccess { Read = 1, Write = 2, ReadWrite = Read | Write }

60. Reverse Mapping (Numeric)

ts

enum Colors { Red, Green, Blue }
console.log(Colors[Colors.Red]); // 'Red'

Union, Intersection, Type Guards

61. Union Parameter

ts

function printId(id: number | string): void {
  console.log(`ID: ${id}`);
}

62. typeof Narrowing

ts

function getLength(value: string | number): number {
  return typeof value === 'string' ? value.length : value.toString().length;
}

63. Discriminated Union

ts

interface Circle { kind: 'circle'; radius: number; }
interface Square { kind: 'square'; side: number; }
type Shape2 = Circle | Square;
function area(shape: Shape2): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'square': return shape.side * shape.side;
  }
}

Also try it: 100 React Native practice problems with solutions

64. User-Defined Type Guard

ts

function isString(value: any): value is string {
  return typeof value === 'string';
}
if (isString(x)) { console.log(x.toUpperCase()); } // x is string

65. instanceof Narrowing

ts

class Cat { meow() { console.log('meow'); } }
class Dog { bark() { console.log('woof'); } }
function makeSound(animal: Cat | Dog): void {
  if (animal instanceof Cat) animal.meow();
  else animal.bark();
}

66. in Operator Narrowing

ts

interface Bird { fly(): void; }
interface Fish { swim(): void; }
function move(animal: Bird | Fish): void {
  if ('fly' in animal) animal.fly();
  else animal.swim();
}

67. Literal Type

ts

type Alignment = 'left' | 'center' | 'right';
function setAlignment(align: Alignment): void { console.log(align); }

68. Optional Chaining & Nullish Coalescing

ts

function getUsername(user: { name?: string | null }): string {
  return user.name ?? 'Anonymous';
}

69. Non-null Assertion (Use with Caution)

ts

let maybeString: string | null = 'hello';
console.log(maybeString!.toUpperCase()); // risky
// Better:
if (maybeString) console.log(maybeString.toUpperCase());

70. Result Discriminated Union

ts

type Result<T> = { success: true; data: T } | { success: false; error: string };
function handleResult<T>(result: Result<T>): T | string {
  return result.success ? result.data : result.error;
}

Utility Types

71. Partial Usage

ts

interface UserI { name: string; age: number; email: string; }
function updateUser(user: UserI, updates: Partial<UserI>): UserI {
  return { ...user, ...updates };
}

72. Required Usage

ts

type StrictUser = Required<UserI>;

73. Pick

ts

type UserContact = Pick<UserI, 'name' | 'email'>;

74. Omit

ts

type UserInfo = Omit<UserI, 'age'>;

75. Record

ts

type PageInfo = Record<string, number>;
const views: PageInfo = { home: 100 };

76. Exclude / Extract

ts

type All = 'a' | 'b' | 'c' | 1 | 2;
type OnlyStrings = Extract<All, string>;      // 'a' | 'b' | 'c'
type NotNumbers = Exclude<All, number>;      // 'a' | 'b' | 'c'

77. NonNullable

ts

type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string

78. Built-in ReturnType

ts

function getData(): { id: number; name: string } { return { id: 1, name: 'Test' }; }
type Data = ReturnType<typeof getData>; // { id: number; name: string }

79. Built-in Parameters

ts

function createUser(name: string, age: number): void {}
type CreateUserArgs = Parameters<typeof createUser>; // [string, number]

80. Built-in InstanceType

ts

class Animal2 { constructor(public species: string) {} }
type AnimalInstance = InstanceType<typeof Animal2>; // Animal2

Mapped & Conditional Types

81. Getters Mapped Type

ts

type Getters<T> = { [K in keyof T]: () => T[K] };

82. Key Remapping (as)
Add on prefix and convert value to callback.

ts

type Prefixed<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};
interface Events { click: string; hover: number; }
type PrefixedEvents = Prefixed<Events>; // { onClick: (val: string) => void; onHover: (val: number) => void; }

83. Template Literal Types

ts

type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'

84. infer in Conditional

ts

type ArrayType<T> = T extends (infer U)[] ? U : never;
type Num = ArrayType<number[]>; // number
type Str = ArrayType<string>;   // never

85. DeepReadonly (Recursive)

ts

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

86. Distributive Conditional Types

ts

type ToArray<T> = T extends any ? T[] : never;
type Arr = ToArray<string | number>; // string[] | number[]

87. Variadic Tuple Types

ts

type Concat<T extends any[], U extends any[]> = [...T, ...U];
type R2 = Concat<[1,2], [3,4]>; // [1,2,3,4]

88. Head / Tail of Tuple

ts

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
type H = Head<[1,2,3]>; // 1
type T = Tail<[1,2,3]>; // [2,3]

89. Recursive JSON Type

ts

type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };

90. Awaited Utility

ts

type PromiseValue = Awaited<Promise<number>>; // number

Decorators

91. Class Decorator (sealed)

ts

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}
@sealed
class Greeter {
  greeting: string;
  constructor(message: string) { this.greeting = message; }
  greet() { return `Hello, ${this.greeting}`; }
}

92. Method Decorator (Log)

ts

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Called ${propertyKey} with`, args);
    return original.apply(this, args);
  };
}
class Calc {
  @log
  add(a: number, b: number): number { return a + b; }
}

Modules & Namespaces

93. Named Export/Import

ts

// math.ts
export function add(a: number, b: number): number { return a + b; }
export function subtract(a: number, b: number): number { return a - b; }
// main.ts
import { add, subtract } from './math';

94. Default Export

ts

// config.ts
export default { apiUrl: 'https://api.com', timeout: 5000 };
// main.ts
import config from './config';

95. Namespace

ts

namespace Utils {
  export function isNull(value: any): boolean { return value === null; }
}
Utils.isNull(null);

Advanced Patterns

96. Typed Event Emitter

ts

class TypedEventEmitter<Events extends Record<string, any>> {
  private listeners = new Map<keyof Events, Array<(payload: any) => void>>();
  on<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): void {
    const arr = this.listeners.get(event) || [];
    arr.push(listener);
    this.listeners.set(event, arr);
  }
  emit<K extends keyof Events>(event: K, payload: Events[K]): void {
    this.listeners.get(event)?.forEach(fn => fn(payload));
  }
}
interface EventsMap { login: { username: string }; logout: void; }
const emitter = new TypedEventEmitter<EventsMap>();
emitter.on('login', ({ username }) => console.log(username));
emitter.emit('login', { username: 'Alice' });

97. Type-Safe Reducer

ts

type Action<T, P = undefined> = P extends undefined ? { type: T } : { type: T; payload: P };
type CounterAction = Action<'increment'> | Action<'decrement'> | Action<'set', number>;
type Reducer<S, A> = (state: S, action: A) => S;
const counterReducer: Reducer<number, CounterAction> = (state, action) => {
  switch (action.type) {
    case 'increment': return state + 1;
    case 'decrement': return state - 1;
    case 'set': return action.payload;
    default: return state;
  }
};

98. Type-Safe Query Builder

ts

class QueryBuilder<T extends Record<string, any>> {
  private fields: (keyof T)[] = [];
  select(...columns: (keyof T)[]): this { this.fields = columns; return this; }
  build(): string { return `SELECT ${this.fields.join(', ')} FROM table`; }
}
interface UserTable { id: number; name: string; age: number; }
new QueryBuilder<UserTable>().select('id', 'name').build();

99. Type-Safe Request Handler

ts

type Handler<Req, Res> = (req: Req) => Res;
class Router<Routes extends Record<string, { req: any; res: any }>> {
  private handlers = new Map<string, Handler<any, any>>();
  addRoute<K extends string & keyof Routes>(path: K, handler: Handler<Routes[K]['req'], Routes[K]['res']>): void {
    this.handlers.set(path, handler);
  }
  handle(path: string, req: any): any {
    const handler = this.handlers.get(path);
    if (handler) return handler(req);
    throw new Error('Not found');
  }
}

Also try it: 100 Dart practice problems with solutions

100. satisfies Operator (TS 4.9+)

ts

type ColorPalette = { primary: string; secondary: string; };
const palette = {
  primary: '#ff0000',
  secondary: '#00ff00',
  tertiary: '#0000ff' // extra property allowed
} satisfies ColorPalette;
// palette.primary is inferred as string; tertiary is accessible.

Final Thoughts

Completing these 100 TypeScript practice problems with solutions is not just about learning syntax—it’s about building confidence and becoming a better developer step by step. Each problem you solved helped you understand types, logic, and structure more clearly.

Some challenges may have felt confusing at first, but every mistake you made became a lesson that made you stronger. Remember, learning TypeScript is a journey, and every line of code you write brings you closer to mastery.

Keep practicing, stay patient, and trust your progress. With consistency and dedication, you will turn your efforts into real skills and real success. Your TypeScript journey has just begun—keep moving forward and never stop learning.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top