Skip to content

Typescript Exercises

Base library types

es5.d.ts

Partial

Make all properties in T optional

ts
interface Base {
  a: number;
  b: string;
  c: boolean;
}

type Result = Partial<Base>

// interface Result { a?: number; b?: string; c?: boolean; }
ts
// source code in es5.d.ts
type Partial<T> = {
    [P in keyof T]?: T[P];
};

Required

Make all properties in T required

ts
interface Base {
  a?: number;
  b?: string;
  c?: boolean;
}

type Result = Required<Base>

// interface Result { a: number; b: string; c: boolean; }
ts
// source code in es5.d.ts
type Required<T> = {
    [P in keyof T]-?: T[P];
};

Readonly

Make all properties in T readonly

ts
interface Base {
  a: number;
  b: string;
  c: boolean;
}

type Result = Readonly<Base>

// interface Result { readonly a: number; readonly b: string; readonly c: boolean; }
ts
// source code in es5.d.ts
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

Pick

From T, pick a set of properties whose keys are in the union K

ts
interface Base {
  a: number;
  b: string;
  c: boolean;
}

type Result = Pick<Base, 'a' | 'b'>

// interface Result { a: number; b: string; }
ts
// source code in es5.d.ts
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Record

Construct a type with a set of properties K of type T

ts
type BaseKeys = 'a' | 'b' | 'c';

type Result = Record<BaseKeys, string | number>

// interface Result {
//   a: string | number;
//   b: string | number;
//   c: string | number;
// }
ts
// source code in es5.d.ts
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Exclude

Exclude from T those types that are assignable to U

ts
type Base = 'a' | 'b' | 'c' | 'd';

type Result = Exclude<Base, 'a' | 'b'>

// type Result = 'c' | 'd';
ts
// source code in es5.d.ts
type Exclude<T, U> = T extends U ? never : T;

Extract

Extract from T those types that are assignable to U

ts
type Base = 'a' | 'b' | 'c' | 'd';

type Result = Extract<Base, 'c' | 'd'>

// type Result = 'c' | 'd';
ts
// source code in es5.d.ts
type Extract<T, U> = T extends U ? T : never;

Omit

Construct a type with the properties of T except for those in type K.

ts
interface Base {
  a: number;
  b: string;
  c: boolean;
}

type Result = Omit<Base, 'a'>

// interface Result { b: string; c: boolean; }
ts
// source code in es5.d.ts
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

NonNullable

Exclude null and undefined from T

ts
type Base = 'a' | 'b' | null | undefined

type Result = NonNullable<Base>

// type Result = 'a' | 'b'

Parameters

Obtain the parameters of a function type in a tuple

ts
function base(a: number, b: string, c: boolean) { return }

type Result = Parameters<typeof base>

// type Result = [a: number, b: string, c: boolean]
ts
// source code in es5.d.ts
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

ConstructorParameters

Obtain the parameters of a constructor function type in a tuple

ts
class Base {
  constructor(a: number, b: string, c: boolean) {
    return a + b + c;
  }
}

type Result = ConstructorParameters<typeof Base>

// type Result = [a: number, b: string, c: boolean]
ts
// source code in es5.d.ts
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

ReturnType

Obtain the return type of a function type

ts
function base() { return { a: 1, b: 'hello' } }

type Result = ReturnType<typeof base>

// type Result = { a: number, b: string }
ts
// source code in es5.d.ts
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

InstanceType

Obtain the return type of a constructor function type

ts
class Base {
  a: number;
  b: string;
  constructor() { }
}

type Result = InstanceType<typeof Base>

// type Result = { a: number, b: string }
ts
// source code in es5.d.ts
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

Uppercase

Convert string literal type to uppercase

ts
type Base = 'foo' | 'bar'

type Result = Uppercase<Base>

// type Result = 'FOO' | 'BAR'
ts
// source code in es5.d.ts
type Uppercase<S extends string> = intrinsic;

Lowercase

Convert string literal type to lowercase

ts
type Base = 'FOO' | 'BAR'

type Result = Lowercase<Base>

// type Result = 'foo' | 'bar'
ts
// source code in es5.d.ts
type Lowercase<S extends string> = intrinsic;

Capitalize

Convert first character of string literal type to uppercase

ts
type Base = 'foo' | 'bar'

type Result = Capitalize<Base>

// type Result = 'Foo' | 'Bar'
ts
// source code in es5.d.ts
type Capitalize<S extends string> = intrinsic;

Uncapitalize

Convert first character of string literal type to lowercase

ts
type Base = 'FOO' | 'BAR'

type Result = Uncapitalize<Base>

// type Result = 'fOO' | 'bAR'
ts
// source code in es5.d.ts
type Uncapitalize<S extends string> = intrinsic;

NoInfer

Marker for non-inference type position

ts
declare function createFSM<T extends string>(config: {
  initial: T;
  states: NoInfer<T>[];
}): T;

const result = createFSM({
  initial: 'd',
  states: ['a', 'b', 'c'],  // Type '"a"' is not assignable to type '"d"'.ts(2322)
});
ts
declare function createFSM<T extends string>(config: {
  initial: T;
  states: NoInfer<T>[];
}): T;

const result = createFSM({
  initial: 'd',  // Type '"d"' is not assignable to type '"a"'.ts(2322)
  states: ['a', 'b', 'c'],
});
ts
// source code in es5.d.ts
type NoInfer<T> = intrinsic;

Exercises

link: https://typescript-exercises.github.io/#exercise=1&file=%2Findex.ts

Demo1

ts
export interface User {
  name: string;
  age: number;
  occupation: string;
}

export const users: User[] = [
  {
    name: 'Max Mustermann',
    age: 25,
    occupation: 'Chimney sweep'
  },
  {
    name: 'Kate Müller',
    age: 23,
    occupation: 'Astronaut'
  }
];

export function logPerson(user: User) {
  console.log(` - ${user.name}, ${user.age}`);  
}

console.log('Users:');
users.forEach(logPerson);

Demo2

link: https://typescript-exercises.github.io/#exercise=2&file=%2Findex.ts

ts

interface User {
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  name: string;
  age: number;
  role: string;
}

export type Person = User | Admin;  

export const persons: Person[] = [
  {
    name: 'Max Mustermann',
    age: 25,
    occupation: 'Chimney sweep'
  },
  {
    name: 'Jane Doe',
    age: 32,
    role: 'Administrator'
  },
  {
    name: 'Kate Müller',
    age: 23,
    occupation: 'Astronaut'
  },
  {
    name: 'Bruce Willis',
    age: 64,
    role: 'World saver'
  }
];

export function logPerson(person: Person) {
  console.log(` - ${person.name}, ${person.age}`);
}

persons.forEach(logPerson);

Demo3

link: https://typescript-exercises.github.io/#exercise=3&file=%2Findex.ts

ts

interface User {
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  name: string;
  age: number;
  role: string;
}

export type Person = User | Admin;  

export const persons: Person[] = [
  {
    name: 'Max Mustermann',
    age: 25,
    occupation: 'Chimney sweep'
  },
  {
    name: 'Jane Doe',
    age: 32,
    role: 'Administrator'
  },
  {
    name: 'Kate Müller',
    age: 23,
    occupation: 'Astronaut'
  },
  {
    name: 'Bruce Willis',
    age: 64,
    role: 'World saver'
  }
];

export function logPerson(person: Person) {
  let additionalInformation: string;
  if ('role' in person) {  
    additionalInformation = person.role;
  } else {
    additionalInformation = person.occupation;
  }
  console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

persons.forEach(logPerson);

Demo4

ts
interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

export type Person = User | Admin;  

export const persons: Person[] = [
  { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
  { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
  { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
  { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }
];

export function isAdmin(person: Person): person is Admin {  
  return person.type === 'admin';
}

export function isUser(person: Person): person is User {  
  return person.type === 'user';
}

export function logPerson(person: Person) {
  let additionalInformation: string = '';
  if (isAdmin(person)) {  
    additionalInformation = person.role;
  }
  if (isUser(person)) {  
    additionalInformation = person.occupation;
  }
  console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

console.log('Admins:');
persons.filter(isAdmin).forEach(logPerson);

console.log();

console.log('Users:');
persons.filter(isUser).forEach(logPerson);

Demo5

ts

interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

export type Person = User | Admin;  

export const persons: Person[] = [
  { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
  {
    type: 'admin',
    name: 'Jane Doe',
    age: 32,
    role: 'Administrator'
  },
  {
    type: 'user',
    name: 'Kate Müller',
    age: 23,
    occupation: 'Astronaut'
  },
  {
    type: 'admin',
    name: 'Bruce Willis',
    age: 64,
    role: 'World saver'
  },
  {
    type: 'user',
    name: 'Wilson',
    age: 23,
    occupation: 'Ball'
  },
  {
    type: 'admin',
    name: 'Agent Smith',
    age: 23,
    role: 'Administrator'
  }
];

export const isAdmin = (person: Person): person is Admin => person.type === 'admin';
export const isUser = (person: Person): person is User => person.type === 'user';

export function logPerson(person: Person) {
  let additionalInformation = '';
  if (isAdmin(person)) {
    additionalInformation = person.role;
  }
  if (isUser(person)) {
    additionalInformation = person.occupation;
  }
  console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

export function filterUsers(persons: Person[], criteria: Partial<Omit<User, 'type'>>): User[] {  
  return persons.filter(isUser).filter((user) => {
    const criteriaKeys = Object.keys(criteria) as (keyof Omit<User, 'type'>)[];  
    return criteriaKeys.every((fieldName) => {
      return user[fieldName] === criteria[fieldName];
    });
  });
}

console.log('Users of age 23:');

filterUsers(
  persons,
  {
    age: 23
  }
).forEach(logPerson);

Demo6

ts
interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

export type Person = User | Admin;  

export const persons: Person[] = [
  { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
  { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
  { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
  { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
  { type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' },
  { type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' }
];

export function logPerson(person: Person) {
  console.log(
    ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
  );
}

const getObjectKeys = <T extends object>(obj: T) => Object.keys(obj) as (keyof T)[];  

export function filterPersons(persons: Person[], personType: User['type'], criteria: Partial<Omit<User, 'type'>>): User[];  
export function filterPersons(persons: Person[], personType: Admin['type'], criteria: Partial<Omit<Admin, 'type'>>): Admin[];  
export function filterPersons(persons: Person[], personType: Person['type'], criteria: Partial<Person>): Person[] {  
  return persons
    .filter((person) => person.type === personType)
    .filter((person) => {
      let criteriaKeys = getObjectKeys(criteria);  
      return criteriaKeys.every((fieldName) => {
        return person[fieldName] === criteria[fieldName];
      });
    });
}

export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 });
export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 });

console.log('Users of age 23:');
usersOfAge23.forEach(logPerson);

console.log();

console.log('Admins of age 23:');
adminsOfAge23.forEach(logPerson);

Demo7

ts
interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

function logUser(user: User) {
  const pos = users.indexOf(user) + 1;
  console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`);
}

function logAdmin(admin: Admin) {
  const pos = admins.indexOf(admin) + 1;
  console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`);
}

const admins: Admin[] = [
  {
    type: 'admin',
    name: 'Will Bruces',
    age: 30,
    role: 'Overseer'
  },
  {
    type: 'admin',
    name: 'Steve',
    age: 40,
    role: 'Steve'
  }
];

const users: User[] = [
  {
    type: 'user',
    name: 'Moses',
    age: 70,
    occupation: 'Desert guide'
  },
  {
    type: 'user',
    name: 'Superman',
    age: 28,
    occupation: 'Ordinary person'
  }
];

export function swap<T1, T2>(v1: T1, v2: T2): [T2, T1] {  
  return [v2, v1];  
}  

function test1() {
  console.log('test1:');
  const [secondUser, firstAdmin] = swap(admins[0], users[1]);
  logUser(secondUser);
  logAdmin(firstAdmin);
}

function test2() {
  console.log('test2:');
  const [secondAdmin, firstUser] = swap(users[0], admins[1]);
  logAdmin(secondAdmin);
  logUser(firstUser);
}

function test3() {
  console.log('test3:');
  const [secondUser, firstUser] = swap(users[0], users[1]);
  logUser(secondUser);
  logUser(firstUser);
}

function test4() {
  console.log('test4:');
  const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]);
  logAdmin(firstAdmin);
  logAdmin(secondAdmin);
}

function test5() {
  console.log('test5:');
  const [stringValue, numericValue] = swap(123, 'Hello World');
  console.log(` - String: ${stringValue}`);
  console.log(` - Numeric: ${numericValue}`);
}

[test1, test2, test3, test4, test5].forEach((test) => test());

Demo8

ts
interface User {
  type: 'user';
  name: string;
  age: number;
  occupation: string;
}

interface Admin {
  type: 'admin';
  name: string;
  age: number;
  role: string;
}

type PowerUser = Omit<User, 'type'> & Omit<Admin, 'type'> & { type: 'powerUser' };  

export type Person = User | Admin | PowerUser;

export const persons: Person[] = [
  { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
  { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
  { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
  { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
  {
    type: 'powerUser',
    name: 'Nikki Stone',
    age: 45,
    role: 'Moderator',
    occupation: 'Cat groomer'
  }
];

function isAdmin(person: Person): person is Admin {
  return person.type === 'admin';
}

function isUser(person: Person): person is User {
  return person.type === 'user';
}

function isPowerUser(person: Person): person is PowerUser {
  return person.type === 'powerUser';
}

export function logPerson(person: Person) {
  let additionalInformation: string = '';
  if (isAdmin(person)) {
    additionalInformation = person.role;
  }
  if (isUser(person)) {
    additionalInformation = person.occupation;
  }
  if (isPowerUser(person)) {
    additionalInformation = `${person.role}, ${person.occupation}`;
  }
  console.log(`${person.name}, ${person.age}, ${additionalInformation}`);
}

console.log('Admins:');
persons.filter(isAdmin).forEach(logPerson);

console.log();

console.log('Users:');
persons.filter(isUser).forEach(logPerson);

console.log();

console.log('Power users:');
persons.filter(isPowerUser).forEach(logPerson);

Demo9

ts