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 ParametersconcatStrings(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 ParameterssumAll(...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 ParameterformatDate(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.