Type Challenge

Easy

Pick

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

Constructs a type by picking the set of properties K from T

For example

interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false
}

answer

type MyPick<T, U extends keyof T> = {
[K in U]: T[K]
}

不知道的知识点

extends

TypeScript 2.8 引入了条件关键字:extends

T extends U ? X : Y

如果T包含的类型是U包含类型的子集,那么取结果 X,否则取结果Y

keyof

TypeScript允许我们遍历某种类型属性,并通过keyof操作符提取其属性的名称。keyof操作符可以用于获取某种类型的所有键,其返回类型是联合类型

interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number

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.

For example

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

answer

type MyReadonly<T> = {
readonly [key in keyof T]: T[key]
}

Tuple to Object

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

For example

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'}

answer

type TupleToObject<T extends readonly Array<string | number | symbol>> = {
[P in T[number]]: P;
}

First of Array

Implement a generic First<T> that takes An Array T and return it's first element's type

For example

type arr1 = ['a', 'b', 'c'];
type arr2 = [3, 2, 1];
type head1 = First<arr1>

answer

type First<T extends readonly unknown[]> =
T extends [infer ElementType, ...infer Tail]
? Element
: never

知识点 - infer

infer可以在extends的条件语句中推断待推断的类型。

作用
  • infer 解包

    // 获取数组元素的类型,在不会 infer 之前是这样做的
    type Ids = number[];
    type Names = string[];
    type Unpacked<T> = T extends Names ? String : T extends Ids ? number : T;
    type idType = Unpacked<Ids>; // idType 类型为 number
    type nameType = Unpacked<Names>; // nameType 类型为 string
    type Unpacked<T> = T extends (infer R)[] ? R : T;
    type idType = Unpacked<Ids>; // idType 类型为 number;
    type nameType = Unpacked<Names>; // nameType 类型为 string;
  • 推断联合类型

    type Foo<T> = T extends {a: infer U; b: infer U} ? U : never;
    type T10 = Foo<{ a: string; b: string }>; // T10 类型为 string.
    type T11 = Foo<{ a: string; b: number }>; // T11 类型为 string | number.

Length Of Tuple

For given a tuple, you need to create a generic Length, pick the length of the tuple

For example

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

answer

// answer1
type Length<T extends readonly any[]> = T["length"]
// answer2
type Length<T> = T extends any[] ? T['length'] : never;
// answer3
type Length = T extends {length: infer Length} ? Length : never;

Exclude

Implement the built-in Exclude<T, U>

Exclude from T those types that are assignable to U

answer

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

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

This question is ported from the original article by @maciejsikora

answer

// answer1
type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;
// answer2
type MyAwaited<T extends Promise<any>> = T extends Promise<infer R> ? (R extends Promise<any> ? MyAwaited<R> : R) : never;

If

Implement utils If which accepts condition C, a truthy return type T, and a falsy return type F. C is expected to be either true or false while T and F can be any type.

For example:

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

answer

type If<C extends boolean, T, F> = C extends true ? T : F;

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

For example

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

answer

type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]

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.

For example

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

answer

type Includes<T extends readonly any[], U> = T extends [infer X, ...infer Y] ? Equal<X, U> extends true ? true : Includes<Y, U> : false;

Push

Implement the generic version of Array.push

For example

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

answer

type Push<T extends unknown[], U> = [...T, U];

Unshift

Implement the type version of Array.unshift

For example

type Result = Unshift<[1, 2], 0> // [0, 1, 2,]

answer

type Unshift<T extends unknown[], U> = [U, ...T]

Parameters

Implement the built-in Parameters generic without using it.

answer

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer X) => any ? X : never;
type MyParameters<T> = T extends (...args: infer P ) => any ? P : T

Medium

Get Return Type

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

For example

const fn = (v: boolean) => {
if (v) {
return 1
} else {
return 2
}
}
type a = MyReturnType<typeof fn> // should "1" | "2"

answer

// answer1
type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;
// answer2
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : 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

For example

interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false
}

answer

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

Readonly 2

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>.

For example

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

answer

type MyReadonly2<T, K extends keyof T = keyof T> =
{ readonly [key in K]: T[key] }
& { [key in keyof T as (key extends k ? never : key)] : T[key] }

// Todo need to understand

Deep Readonly

Implement a generic DeepReadonly<T> which makes 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 take into consideration. However, you can still challenge yourself by covering different cases as many as possible.

For example.

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`

answer

// answer1
type DeepReadonly<T extends Record<any, any>> = {
readonly [key in keyof T]: T[key] extends string | number | symbol | Function | boolean
? T[key]
: DeepReadonly<T[key]>
}
// answer2
type DeepReadonly<T> = {
readonly [key in keyof T] : T[key] extends Function ? T[key] : T[key] extends object ? DeepReadonly<T[key]> : T[key];
}

Tuple to Union

Implement a generic TupleToUnion<T> which covers the values of a tuple to its values union

For example

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

answer

// answer1
type TupleToUnion<T extends readonly unknown[]> = T extends [
infer First,
...infer Rest
]
? First | TupleToUnion<Rest>
: never
// answer2
type TupleToUnion<T extends unknown[]> = T[number]

Chainable Options

Chainable options are commonly used in JavaScript. But when we switch to TypeScript, can you property 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.

For example

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

You don't need to write any js/ts logic to handle the problem - just in type level.

You can assume that key only accepts string and the value can be anything - just leave it as-is. Same key won't be passed twice.

answer

type chainable<T extends Record<string, unknown> = {}> = {
option<K extends string, V = unknown>(key: K, value: V): Chainable<T & Record<K, V>>
get(): T
}

Last of Array

TypeScript 4.0 is recommended in this challenge

Implement a generic Last<T> that takes an Array T and returns its last element.

For example

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1

answer

// answer1
type Last<T extends any[]> = T extends [infer L, ...infer R] ? (R[0] extends undefined ? L : Last<R>) : never;
// answer2
type LastOfArray<T extends readonly any[]> = T extends [...any[], infer R] ? R : never;

Pop

TypeScript 4.0 is recommended in this challenge

Implement a generic Pop<T> that takes an Array T and returns an Array without it's last element.

For example

type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]
type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]

Extra: Similarly, can you implement Shift, Push, and Unshift as well?

answer

type Pop<T extends any[]> = T extends [...infer R, any] ? R : never;

Promise.all

Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// expected to be `Promise<[number, 42, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)

answer

// answer1
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{
[K in keyof T]: Awaited<T[K]>
}>
// answer2
type PromiseType<T> = T extends Promise<infer PT> ? PT: T
type PromisesType<T extends readonly any[]> = Promise<T extends readonly [infer First, ...infer Rest]
? [PromiseType<First>, ...PromisesType<Rest>]
: []>
declare const PromiseAll: <T extends readonly any[]>(promises: T) => PromisesType<T>

Type LookUp

Sometimes, you may want to lookup for a type in a union to by their attributes.

In this challenge, we would like to get the corresponding type by searching for the common type field in the union Cat | Dog. In other words, we will expect to get Dog for LookUp<Dog | Cat, 'dog'> and Cat for LookUp<Dog | Cat, 'cat'> in the following example.

interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDogType = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`

answer

type LookUp<U, T extends string> = U extends Record<string, any> ? U['type'] extends T ? U : number : never

Trim Left

Implement TrimLeft<T> which takes an exact string type and returns a new string with the whitespace beginning removed.

For example

type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '

answer

type Space = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Space}${infer R}` ? TrimLeft<R> : S

Trim

Implement Trim<T> which takes an exact string type and returns a new string with the whitespace from both ends removed.

For example

type trimmed = Trim<' Hello World '> // expected to be 'Hello World'

answer

Hard

Extreme