Skip to content

ts

reference

type-challenges

easy

pick

Implement the built-in Pick<T, K> generic without using it.

  • example:
js
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}
  • solutions:
ts
export type MyPick<T,K extends keyof T> = {
    [P in K]: T[P]
}
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

console.log("easy:MyPick", todo)

tupleToObject

Give an array, transform into an object type and the key/value must in the given array.

  • example:
ts
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
  • solutions:
ts
export type TupleToObject<T extends ReadonlyArray<number | string>> = {
  [K in T[number]]: K;
};

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;

type result = TupleToObject<typeof tuple>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

let value: result = {
  tesla: 'tesla',
  'model 3': 'model 3',
  'model X': 'model X',
  'model Y': 'model Y',
};

console.log('easy', 'tupleToObject', value);

readonly

Implement the built-in Readonly<T> generic without using it.

Constructs a type with all properties of T set to readonly, meaning the properties of the constructed type cannot be reassigned.
  • example:
ts
interface Todo {
  title: string
  description: string
}

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar"
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
  • solutions:
ts
export type MyReadonly<T> = {
    readonly [P in keyof T]: T[P]
}


interface Todo {
    title: string
    description: string
  }
  
  const todo: MyReadonly<Todo> = {
    title: "Hey",
    description: "foobar"
  }
  
//   todo.title = "Hello" // Error: cannot reassign a readonly property
//   todo.description = "barFoo" // Error: cannot reassign a readonly property

  console.log("easy: readonly", todo)

Length of Tuple

For given a tuple, you need create a generic Length, pick the length of the tuple
  • example:
ts
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla>  // expected 4
type spaceXLength = Length<spaceX> // expected 5
  • solutions:
ts
export type MyTupleLength<T extends readonly any[]> = T['length']

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type TeslaLength = MyTupleLength<tesla>  // expected 4
type SpaceXLength = MyTupleLength<spaceX> // expected 5

const tLen: TeslaLength = 4;
const sLen: SpaceXLength = 5;

console.log("easy: myTupleLength", tLen, sLen)

Exclude

Implement the built-in Exclude<T, U>
Exclude from T those types that are assignable to U
  • example:
ts
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
  • solutions:
ts

export type MyExclude<T, U> = T extends U ? never : T;

type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

const value: Result = 'b'

console.log("medium:exclude",value)

Awaited

If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type?

For example: if we have Promise<ExampleType> how to get ExampleType?
  • example:
ts
type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string
  • solutions:
ts
export type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer R> ? R extends PromiseLike<any> ? MyAwaited<R> : R : never

type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string


let value: Result = '1';

console.log("easy: awaited", value)

If

Implement the util type If<C, T, F> which accepts condition C, a truthy value T, and a falsy value F. C is expected to be either true or false while T and F can be any type.
  • example:
ts
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
  • solutions:
ts

export type If<C extends Boolean, A, B> = C extends true? A : B

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

const aValue: A = 'a';

console.log("easy-8: If", aValue)

Concat

Implement the JavaScript Array.concat function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr order
  • example:
ts
type Result = Concat<[1], [2]> // expected to be [1, 2]
  • solutions:
ts
export type Concat<T extends any[], K extends any[]> = [...T , ...K]

type Result = Concat<[1], [2]> // expected to be [1, 2]

const value: Result = [1, 2]

console.log('easy: concat', value)

Includes

Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.
  • example:
ts
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
  • solutions:
ts

// export type Includes1<T extends any[], V> = V extends T[number] ? true : false

export type Includes<T extends any[], V> = V extends T[number]? true : false

// type Exact<A, B> = (<G>() => G extends A ? 1 : 2) extends
//   (<G>() => G extends B ? 1 : 2) ? true : false;
// type Includes<T extends readonly any[], U> = T extends [infer Head, ...infer Tail] ? Exact<Head,U> extends true ? true : Includes<Tail, U> : false


type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

const value: isPillarMen = false


console.log('easy: includes', value)

Push

txt
Implement the generic version of Array.push
  • example:
ts
type Result = Push<[1, 2], '3'> // [1, 2, '3']
  • solutions:
ts
export type Push<T extends unknown[], U> = [...T, U]

type Result = Push<[1, 2], '3'> // [1, 2, '3']

const value: Result = [1, 2, '3']

console.log('easy-11: push', value)

medium

Get Return Type

Implement the built-in ReturnType<T> generic without using it.

  • example
ts
const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}

type a = MyReturnType<typeof fn> // should be "1 | 2"
  • solutions:
ts
export type MyReturnType<T extends (...args: any[]) => unknown> = T extends (...args: any[]) => infer U ? U : never

Omit

Implement the built-in Omit<T, K> generic without using it.
Constructs a type by picking all properties from T and then removing K
  • example:
ts
interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
  completed: false,
}
  • solutions:
ts

export type MyOmit<T, K extends keyof T> = {
    [P in keyof T as P extends K ? never : P]: T[P]
}


interface Todo {
    title: string
    description: string
    completed: string
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todoValue: TodoPreview = {
    completed: "hi, my omit type!"
}

console.log("medium:", "MyOmit", todoValue)

Readonly2

Implement a generic MyReadonly2<T, K> which takes two type argument T and K.

K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties readonly just like the normal Readonly<T>.
  • example:
ts
interface Todo {
  title: string
  description: string
  completed: boolean
}

const todo: MyReadonly2<Todo, 'title' | 'description'> = {
  title: "Hey",
  description: "foobar",
  completed: false,
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK
  • solutions:
ts
export type MyReadonly2<T, P extends keyof T> = {
    readonly [K in P]: T[K]
} & {
    [K in Exclude<keyof T, P>]: T[K]
}

interface Todo {
    title: string
    description: string
    completed: boolean
  }
  
  const todo: MyReadonly2<Todo, 'title' | 'description'> = {
    title: "Hey",
    description: "foobar",
    completed: false,
  }
  
//   todo.title = "Hello" // Error: cannot reassign a readonly property
//   todo.description = "barFoo" // Error: cannot reassign a readonly property
  todo.completed = true // OK
  console.log('medium: MyReadonly2', todo)

Deep Readonly

Implement a generic DeepReadonly<T> which make every parameter of an object - and its sub-objects recursively - readonly.

You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on do not need to be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.
  • example:
ts
type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

type Todo = DeepReadonly<X> // should be same as `Expected`
  • solutions:
ts
export type DeepReadonly<T extends { [propName: string]: any }> = {
    readonly [key in keyof T]: T[key] extends { [propName: string]: any } ? DeepReadonly<T[key]> : T[key]
}

type X = {
    x: {
        a: 1
        b: 'hi'
    }
    y: 'hey'
}

type Expected = {
    readonly x: {
        readonly a: 1
        readonly b: 'hi'
    }
    readonly y: 'hey'
}

type Todo = DeepReadonly<X> // should be same as `Expected`


const todoValue: Todo = {
    x: {
        a: 1,
        b: 'hi',
    },
    y: 'hey',
}

// todoValue.x = 3;

console.log('medium: deep readonly', todoValue)

Tuple to Union

Implement a generic TupleToUnion<T> which covers the values of a tuple to its values union.
  • example:
ts
type Arr = ['1', '2', '3']

type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
  • solutions:
ts
export type TupleToUnion<T extends any[]> = T extends [infer i,...infer test] ? i | TupleToUnion<test> : never


type Arr = ['1', '2', '3']

type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'

const testValue: Test = '1';

console.log("medium: tupleToUnion", testValue);

Chainable Options

Chainable options are commonly used in Javascript. But when we switch to TypeScript, can you properly type it?

In this challenge, you need to type an object or a class - whatever you like - to provide two function option(key, value) and get(). In option, you can extend the current config type by the given key and value. We should about to access the final result via get.
  • example:
ts
declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}
  • solutions:
ts
export type Chainable<Options = {}> = {
    option<K extends string, V>(
      key: K,
      value: V
    ): Chainable<Options & { [S in K]: V }>;
    get(): { [P in keyof Options]: Options[P] };
  };

// export type Chainable<Options= {}> = {
//     option<K extends string, V>(
//         key: K,
//         value: V
//     ): Chainable<Options & { [S in K]: V } >,
//     get(): {
//         [P in keyof Options]: Options[P]
//     },
// }


const config: Chainable = {
    option: function (){
        return this
    },
    get: function (){
        return {}
    }
} as unknown as Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
// interface Result {
//   foo: number
//   name: string
//   bar: {
//     value: string
//   }
// }

console.log("medium: Chainable", result)