Pick
itself.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md
Given the following TypeScript:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
Given the interface Todo
above, we need to create a generic type MyPick
that takes the type (Todo
) and a union of the properties that we wish to pick from it. The base code would look something like:
type MyPick<T, K> = ?
If we were creating a function in JavaScript to do a similar job, we'd want to loop over everything in K
and construct a new object with each entry from K
as a key. We'd set that key value equal to T[i]
to reference the original value of T for that key.
We can do something similar in TypeScript types with mapped types. The syntax for a mapped type is:
type MyPick<T, K> = {
[Property in K]: T[Property]
}
Where property is bound to each entry in our K
type union.
The above creates a new type from the existing type T
, with only the keys from K
in it, as it loops over the entries in K
only. However it doesn't work as TypeScript has no guarantee that the type K is a union type. For example:
MyPick<Todo, number>
Would not through any TypeScript errors as both T
and K
are types, Todo
and number
respectively. Clearly we need to restrict the type of K to be a union type of keys that exist on T.
type MyPick<T, K extends keyof T> = {
[Property in K]: T[Property]
}
This means that even if K is a union, if it includes properties that do not exist on T, it will result in a compiler error.
]]>Readonly
itself.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.md
Given the following TypeScript:
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
We need to create a new type called MyReadonly
that will result in the following operations failing,
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
This means we need to create a generic type that accepts a type as a parameter, and returns a new type with the readonly
property set for each property in that type.
type MyReadonly<T> = ?
We need to create a new type based off an existing type, this means we need to use mapped types in TypeScript.
The basic syntax for a mapped type is:
type MyReadonly<T> = {
[Property in keyof T]: T[Property]
}
Here we use keyof T
to generate a union type of all the properties of T, which we then use to iterate over and generate a new type from. The type that the above returns is exactly the same as the type T
passed in.
Mapped types allow you to add or remove modifiers when you create new types. These modifiers are ?
to control optionality and readonly
to control mutability.
You can add them with +
and remove them with -
.
Therefore we can add the readonly
by prefixing each entry in our new type with the +readonly
modifier.
type MyReadonly<T> = {
+readonly [Property in keyof T]: T[Property]
}
However, when adding modifiers, the +
is optional, so we can simplify this type to:
type MyReadonly<T> = {
readonly [Property in keyof T]: T[Property]
}
]]>
Exclude
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00043-easy-exclude/README.md
For example, given the following cases, our MyExclude type should return the type shown in the comments:
MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
MyExclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
MyExclude<string | number | (() => void), Function> // string | number
In plain english, we want our type to:
loop over everything in
T
, if it doesn't exist inU
include it in our new type
On paper, this sounds very similar to our approach for implementing our own Pick type, and a naive approach might look something like:
type MyExclude<T extends (string | number | symbol), U> = {
[Property in T]: Property extends U ? never : Property
}
Where we loop over everything in T
using a mapped type, and then use a conditional type to either exclude it (returning never
) or include it (returning Property
).
This solution partially works, but results in a type (for our first example case) of:
MyExclude<'a' | 'b' | 'c', 'a'> // { a: never, b: 'b', c: 'c'}
This is along the right lines, and could probably be coerced into the expected shape, but misses the point of this challenge.
To solve this we need to know one of the "super powers" of conditional types in TypeScript, that is they are distributive over unions. What does that mean?
This means when a conditional type is working on a union type, (T extends something
where T is a union type), then the condition is applied to each member of that union.
For example,
type YesOrNo<T extends boolean> = T extends true ? "yes" : "no"
When we call that type with one value:
YesOrNo<true> // "yes"
We get back the single type "yes", but when we call it with a union type:
YesOrNo<true | false> // "yes" | "no"
We get back a union type, where each element in the union has been applied to the type.
So the solution to our MyExclude
problem actually becomes very simple:
type MyExclude<T, U> = T extends U ? never : T
We use a conditional type T extends U
to check if T
exists in U
, if it does, we return never
, otherwise we include it in the resulting object. Returning never
works to exclude items, as never
gets dropped by union types that include it.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00014-easy-first/README.md
For example, given the following cases, our First type should return the type shown in the comments:
First<[3, 2, 1]> // 3
First<[() => 123, { a: string }]> // () => 123
First<[]> // never
First<[undefined]> // undefined
Like in the Tuple To Object, we're interested in the type of a specific element of an array. We can use indexed access types to do this.
We know we can access the type of the first element in an array with T[0]
so our initial implementation can be as simple as:
type First<T extends any[]> = T[0]
This works for 3 out of 4 of our example cases above, but fails for the empty case First<[]>
. We can see that:
First<[]>
is currently returning undefined
but the challenge requires it to return never
.
We can special case the empty array and use conditional types to solve this challenge. We want to say
if the array is empty, return never, otherwise return the type of the first element in our array
The syntax for conditional types looks like:
type First<T extends any[]> = T extends [] ? never : T[0]
Where T extends []
can be read as, if T
is empty.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.md
For example, given the following cases, our Length type should return the type shown in the comments:
Length<typeof tesla> // 4
Length<typeof spaceX> // 5
Length<5> // @ts-expect-error
Length<'hello world'> // @ts-expect-error
Like in the Tuple To Object, we're interested in the type of a specific element of an array. We can use indexed access types to do this.
We know that in TypeScript, like JavaScript arrays are objects, and those objects have a length
property signifying their length. We therefore know that the T["length"]
should give us the length of an array.
type Length<T> = T["length"]
The above works, but throws TypeScript errors, as TypeScript doesn't have a guarantee that the type T
passed into length will be an array, it may therefore not have a length
property.
To solve this we can constrain the type of T to be only arrays:
type Length<T extends any[]> = T["length"]
This solves most of our issues but leaves us with two errors in our example cases above. The errors occur as when we call Length<typeof tesla>
the typeof tesla
is a readonly array. We can't assign a readonly array to a any[]
type, we therefore need to further constrain T
to only allow readonly arrays.
type Length<T extends readonly any[]> = T["length"]
This readonly
constraint makes sense, as if we pass in a mutable array to Length
, the only type that we will be able to get back will be number
, rather than the length of our array. This is because mutable arrays can be changed at runtime, so TypeScript cannot infer the length of it.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.md
For example, given the following interface, our TupleToObject should generate this type.
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'}
Like in the Pick challenge, we need to create a new type based off an existing type, so therefore we can use a mapped types to do so.
Since we want our object type to contain key value pairs that both correspond to one entry in our readonly tuple, we could implement it like so:
type TupleToObject<T extends readonly any[]> = {
[Property in T]: Property
}
If this worked, it would create a new object type, with each key being Property
and each value being Property
, making our test cases pass.
Unfortunately this doesn't work as mapped types can only be used against union types, not readonly tuple types.
So we need to convert our readonly[]
type into a union type, and then loop over this union type.
We know that given for any readonly[]
we could construct it's union type by taking the type of the first element, the second, the third all the way up to the nth, like:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const TupleType = typeof tuple
type union = TupleType[0] | TupleType[1] | TupleType[2] // etc
This works as we can use index types to access the types of each element in the readonly[]
.
This works, but we need to know the length of our tuple upfront, instead we can use the TupleType[number]
syntax to generate a union of all of the types in a readonly[]
tuple.
Using T[number]
to convert our readonly array into a union type, and mapping over that union looks like:
type TupleToObject<T extends readonly any[]> = {
[Property in T[number]]: Property
}
Finally, we need to constrain the type of readonly arrays that we accept. Since at the moment you can pass:
const t = TupleToObject<[[1, 2], {}]>
without the TypeScript compiler complaining, but clearly our TupleToObject
type can't work on this type since [1, 2]
and {}
aren't valid keys in a typescript type.
We need to restrict our any[]
type to be only tuples that contain elements that are valid keys. Valid keys in typescript can be string
, number
or symbol
so our final type becomes:
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[Property in T[number]]: Property
}
]]>
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md
Coming soon
For example, given the following cases, our If type should return the type shown in the comments:
If<true, 'a', 'b'> // 'a'
If<false, 'a', 2> // 2
In plain english, we want our type to:
if C is true, return T, if C is false, return F
This is the textbook use-case for conditional types in TypeScript
We'll start by implementing a conditional type that returns T or F based on the value of C:
type If<C, T, F> = C extends true ? T : F
If C is assignable to true, return F, otherwise return F.
This is almost the complete solution, but at the moment our C
generic can take on any value, we want to constrain it to only accept boolean values. The final solution is:
type If<C extends boolean, T, F> = C extends true ? T : F
]]>
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/README.md
A new test case has been added since I tackled this challenge:
type T = { then: (onfulfilled: (arg: number) => any) => any }
type cases = [
Expect<Equal<MyAwaited<T>, number>>,
]
This is a "promise like" type, without actually being a Promise. Fortunately, TypeScript has a PromiseLike interface covering this. We can replace Promise
with PromiseLike
in our below type and everything works!
For example, given the following cases, our Awaited type should return the type as shown in the promise:
MyAwaited<Promise<string> // string
MyAwaited<Promise<{ field: number }> // { field: number }
MyAwaited<Promise<Promise<string | number>> // string | number
MyAwaited<Promise<Promise<Promise<string | boolean>>> // string | boolean
The first two examples show a simple promise type being passed to MyAwaited
with the type inside that Promise being returned. The third and fourth examples show that our type needs to work with nested promises.
Lets consider only the first example case above:
MyAwaited<Promise<string> // string
We could write a type to resolve this to a string like so:
type MyAwaited<T> = T extends Promise<string> ? string : never;
In english this reads:
if you pass a promise of a string, return a string, otherwise return never
This only works for the first example case, but we could extend it to work for the second case. In english we could say:
if you pass a promise of a string, return a string, otherwise, if you pass a promise of an object type, return that object type, otherwise return never
We can extend our type to handle multiple cases:
type MyAwaited<T> = T extends Promise<string>
? string
: T extends Promise<{ field: number }>
? { field: number }
: never;
This would now work for the first two example cases, and could be extended indefinitely to handle all cases. But if we took this approach to handle all cases, our type would become infinitely long. Instead we need a generic solution to this problem.
When working with conditional types in TypeScript, we can make use of inference within conditional types.
Instead of extending our type, we can replace it with a generic version making use of the infer
keyword:
type MyAwaited<T> = T extends Promise<infer InnerType> ? InnerType : never;
This works for the first two example cases, in the first example InnerType takes on the type string
and returns that, in the second example it takes on the type {field: number}
and we return that.
This still doesn't solve the nested cases. At present, when we pass Promise<Promise<string | number>>
to our type, we currently get back Promise<string | number>
, i.e. it unwraps one level of Promises. We need to unwrap all the promises!
We can extend our type to work recursively:
type MyAwaited<T> = T extends Promise<infer InnerType>
? InnerType extends Promise<any>
? MyAwaited<InnerType>
: InnerType
: never;
Here we check if InnerType is still a promise (after being unwrapped once as per before), if it is, we call MyAwaited
on it again (the recursive call). If InnerType
is no longer a promise, we return it's type.
Finally, we need to constrain the type T
so that we can only pass Promise types to our type, and our final type looks like:
type MyAwaited<T extends Promise<any>> = T extends Promise<infer InnerType>
? InnerType extends Promise<any>
? MyAwaited<InnerType>
: InnerType
: never;
]]>
Includes<T extends unknown[], U>
that returns true if U
is included in T
.
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.md
The easier examples to solve this problem for are shown below:
For the given examples, our Includes type should return the type as shown in comment
Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'> // true
Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // false
Includes<[1, 2, 3, 5, 6, 7], 7> // true
Includes<[1, 2, 3, 5, 6, 7], 4> // false
Includes<[1, 2, 3], 2> // true
Includes<[1, 2, 3], 1> // true
Includes<[false, 2, 3, 5, 6, 7], false> // true
Includes<[null], undefined> // false
Includes<[undefined], null> // false
These examples are all fairly simple, they include an array of types as T
and one specific type as U
. If U is inside T
we return true, otherwise we return false.
Includes<[{}], { a: 'A' }> // false
Includes<[boolean, 2, 3, 5, 6, 7], false> // false
Includes<[true, 2, 3, 5, 6, 7], boolean> // false
Includes<[{ a: 'A' }], { readonly a: 'A' }> // false
Includes<[{ readonly a: 'A' }], { a: 'A' }> // false
Includes<[1], 1 | 2> // false
Includes<[1 | 2], 1> // false
In these more complicated cases, the array T
includes complex types such as objects and type classes. For example in the second complex case, false
is assignable to boolean
but not equal to it, so false
is not inside T
despite boolean
being a member of T
.
Lets forget the complex use example cases for now. If you've been following the challenges, you may have taken the excludes challenge already. This challenge seems very similar although this time we want to return true if U
is in T
, not the other way round.
We know that working with tuple types when doing type manipulation can be simplified by turning a tuple into a union type like so:
T[number]
And once we've turned our tuple type into a union type we can simply ask if U
is assignable to it and write a simple type like so:
type Includes<T extends readonly unknown[], U> = U extends T[number] ? true : false
This converts our type T
to a union of all the members of the tuple, then checks to see if U
is assignable to to it. In this case, assignable to means "is equal to". If U
is, return true, else return false.
This works for the simple cases, but not the more advanced cases. Lets dig into these to find out why
For this example,
Includes<[boolean, 2, 3, 5, 6, 7], false>
Our type is currently returning true, but it should be returning false. Clearly false
isn't in our array, but the Includes type thinks it is. What's going on here?
The problem is with our equality check, we're asking our union type if false
is assignable to it. Well it is! false
is assignable to boolean
, as is true
. Clearly we need a better way of comparing types.
The type challenges come with a set of utility types that are used to make assertions in the playground. One of these types is called Equal. We won't go into how this type works in this post, but know that you can use it to compare the equality of two types:
Equal<true, boolean> // returns false
Equal<false, boolean> // also returns false
Now all we have to do is make use of this type in our Includes
type
To solve this challenge we need to abandon our naive solution and rethink the approach completely. Instead of converting our array into a union type, we're instead going to loop over the elements, and compare the equality of each one to U
using our Equal
type.
Unfortunately there's no looping construct when doing type manipulation, instead we'll have to rely on recursion. Our approach will be:
First
Rest
First
to U
using Equal
Includes
type again with Rest
and the existing U
In my opinion, the hardest part of the steps above is working out how we can grab the first element, and the rest of the elements out of the T
array.
To do this, we can use the infer
keyword. We can use this in a condition type like so:
T extends [infer First, ...infer Rest] ? true : false
infer First
is bound to the first element in the array, ...infer Rest
is bound to everything else in the array, or the empty array if there are no other elements. This works very similarly to how it would in JavaScript.
TypeScript will only match infer First
if there is at least one element in the array, i.e. there is a type for typescript to infer. This means that the condition will return false when we pass the empty array. If there is only one element in the array ...infer Rest
will bind to []
, much like the spread operator in JavaScript.
Turning the steps into code leaves us with:
type Includes<T extends readonly unknown[], U> = T extends [infer First, ...infer Rest]
? Equal<First, U> extends true
? true
: Includes<Rest, U>
: false
We need the additional conditional type to compare the equality of First
and U
to true.
The recursive nature of this means the T
will become one element smaller each time we call it, eventually becoming []
(if we don't find U
and First
). If T
does become []
this is our base case of our recursive type, and we return false
as we fail the initial conditional check.
Coming soon...
It's possible to solve the FizzBuzz interview question using TypeScripts type system alone, without any run time code. I'm going to show you how.
The rules of FizzBuzz are simple:
For each number between 1 and a given number, print:
In normal sane typescript this would be easy enough, something like:
function fizzbuzz(n: number): void {
for (let i = 1; i <= n; i++) {
if (i % 15 === 0) {
console.log("FizzBuzz");
} else if (i % 3 === 0) {
console.log("Fizz");
} else if (i % 5 === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
}
But this isn't all that interesting, instead I want a Type that can do this. I want this wall of types:
type FizzBuzzRange<
A extends number,
B extends number,
Tuple extends unknown[] = []
> = A extends B
? Tuple
: FizzBuzzRange<Add<A, 1>, B, [...Tuple, FizzBuzz<A>]>
type FizzBuzz<N extends number> = And<IsDivisibleBy3<N>, IfDivisibleBy5<N>>
? "FizzBuzz"
: IsDivisibleBy3<N> extends true
? "Fizz"
: IfDivisibleBy5<N> extends true
? "Buzz"
: N
Lets build this piece by piece.
You are going to need to know about:
So this syntax should make sense:
type HelloOrGoodbye<T extends boolean> =
T extends true
? "Hello"
: "Goodbye"
Basically we take a generic type T that's a boolean, if T extends true, which we'll equate to equals true, we return "Hello" otherwise we return false.
type Flatten<T> = T extends Array<infer Item> ? Item : T;
Here we're saying if our generic type T is an array, we want to get back the type of whats inside the array, otherwise give back T itself. The infer keyword is going to be really helpful to us when building types.
Sounds complicated, but isn't really. This can be thought of as similar to the spread operator working on arrays in JavaScript. Think:
type Concat<A extends any[], B extends any[]> = [...A, ...B]
Here we're combining two tuples together by "spreading" both.
Since we don't have loops in the type system. we're going to be using a lot of recursion
Lets break the FizzBuzz problem down to it's core, it's basically can you use the modulo operator? If we could build a Modulo operator in typescript, we'd be able to solve the problem
So assuming we can't use %, how would we build mod?
Well one way that makes sense to me, and a way that's going to work well for our use case is:
function isDivisibleBy(a, b) {
let counter = b;
while (a >= counter) {
if (counter === a) {
return 0;
}
counter += b;
}
return a - (counter - b);
}
A worked example would make sense:
For mod(9, 3)
We start a counter off at 3, then we loop until that counter is bigger than 10, adding three to the counter each time round the loop. Inside the loop we check if our counter === a, if it does we return 0. The first time through the loop a = 9, counter = 3, so this isn't met, the second time through a = 9, counter = 6, the third time however, a = 9, counter = 9, so we'll return the 0, indicating a 0 remainder
For the example mod(10, 3) We'll keep adding 3 to the counter until it reaches 12, at this point 12 is bigger than 10 so we exit the loop. We then need to work out the remainder. We take 3 off 12 to get back to the iteration before, then we do 10 - 9 to work out the remainder.
We know we don't have loops in the type system, but we do have recursion! So before we try and turn that into a type, lets build it with recursion:
function modulo(a, b, counter = b) {
if (counter === a) {
return 0
}
if (a < counter) {
return a - (counter - b);;
}
return modulo(a, b, (counter += b));
}
This is the exact same algorithm. Ok great. No lets turn that into a Type!
type Modulo<A extends number, B extends number, Counter extends number = B> =
A extends Counter
? 0
: A < Counter extends true
? A - (Counter - B)
: Modulo<A, B, Counter + B>
Here we're using extends to mean equals, that's not quite true but it's good enough for us.
I haven't lost the plot, this type isn't going to work. There are 3 problems with it.
Add, LessThan and Minus. We can't use normal operators in our types. We need to find some way of creating types that work like the operators do.
Lets start with Add, as it's the base of the rest.
Lets assume we want to build a type that when given two numbers returns the result of adding them:
type Add<A extends number, B extends number> = ??
Add<1, 2> // 3
Add<9, 3> // 12
Is the goal, we need to come up with a plan.
Adding is like combining two numbers, we can't add two numbers but we can combine two things, tuples!
Imagine we had this code in TS:
function combine_arrays(a : unknown[], b: unknown[]) : unknown[] {
return [...a, ...b]
}
A function that takes two arrays and returns a new array with all the elements from both.
Now if we asked for the length of that new array... it would be the length of array a + array b.
So this is how we could tackle Add. We can combine two tuples into one using variadic tuple types just like we can in JavaScript. If we had a Type that could turn a number into a tuple of that length, and a Type that could return the length of a tuple, we could have a way of building Add.
Lets work on the easy part first, a type that can return the Length of a tuple;
type Length<A extends unknown[]> = A extends { length: infer L} ? L : never
We use a conditional type to infer the length of the tuple and return that.
Now we need a way of building a tuple of a given length. We'll start with a number and aim to build a tuple that includes all of the numbers up to that number, for example:
type Range<5> = [0, 1, 2, 3, 4]
type Range<7> = [0, 1, 2, 3, 4, 5, 6]
We've called it Range as that's pretty much what it's doing. How would we build that?
type Range<N extends number> = ???
Lets take the same approach, build a recursive solution in JS and then turn it into a type:
function range(n, tuple=[]) {
if (tuple.length === n) {
return tuple;
}
return range(n, [...tuple, tuple.length])
}
So we're going to keep growing the tuple by adding a number representing the current length of it and eventually we'll return the tuple when it's the length expected.
In TypeScript that looks like a fairly simple recursive type:
type Range<N extends number, Tuple extends number[] = []> = Length<Tuple> extends N
? Tuple
: Range<N, [...Tuple, Length<Tuple>]>;
So now, building Add is fairly simple, we create a type called Add that takes two numbers,
type Add<A extends number, B extends number> = ???
Then we turn those numbers into tuples with their respective lengths, and we combine them together into one tuple:
type Add<A extends number, B extends number> = [...Range<A>, ...Range<B>];
And finally we return the length using out Length type:
type Add<A extends number, B extends number> = Length<[...Range<A>, ...Range<B>]>;
Ok so looking back at our modulo type, we can now fix it up a little:
type Modulo<A extends number, B extends number, Accumulator extends number = B> =
A extends Accumulator
? 0
: A < Accumulator extends true
? A - (Accumulator - B)
: Modulo<A, B, Accumulator + B>
The + can be replaced by our new Add type:
type Modulo<A extends number, B extends number, Accumulator extends number = B> =
A extends Accumulator
? 0
: A < Accumulator extends true
? A - (Accumulator - B)
: Modulo<A, B, Add<Accumulator + B>>
Our Modulo type looks better now, but we still have two problems, the < sign and the subtraction. Lets focus on the < less than since this is going to be pretty easy to build.
Ideally we'd like a type that we can use like so:
LessThan<A, B>; // boolean
It should take two numbers and return a boolean if A is less than B. But how should we build less than?
Your first instinct might be subtract a from b, and see if that is less than zero. But if we do that we're not closer to being able to build our type in the type system.
Instead, lets think about what we have built so far with our Range type.
If we turn B into the Range, if A exists inside that tuple then it must be less than B, Example time:
// LessThan<2, 5> // Range<5> = [0, 1, 2 ,3, 4] // 2 extends Range<5> // true
A case where it isn't less than:
// LessThan<5, 5> // Range<5> = [0, 1, 2, 3, 4] // 5 extends Range<5> // false as 5 is not less than 5
Ok, we should be able to build that!
type LessThan<A extends number, B extends number> =
A extends Range<B> ? true : false
But this isn't quite right, for example:
// LessThan<1, 2>
The condition is saying is 1 assignable to [1, 2], but that doesn't quite work, instead we need to turn the tuple into a union type to ask if a number is inside it.
// 1 is assignable to 1 | 2
We can do that with the [number]
syntax, so Range<A>[number]
would create a union 0 | 1 | 2 | 3
We have to write this in two steps to stop TypeScript getting confused:
type LessThan<
A extends number,
B extends number,
BRange extends number[] = Range<B>
> = A extends BRange[number] ? true : false
Ok so now back to Modulo, lets replace our < with our new LessThan type:
type Modulo<A extends number, B extends number, Counter extends number = B> =
A extends Counter
? 0
: LessThan<A, Counter> extends true
? A - (Counter - B)
: Modulo<A, B, Add<Counter + B>>
So now there's just one issue left, subtract. Now here me out, we don't actually need subtract to get FizzBuzz working. We don't need to know the modulo to get FizzBuzz working, we only need to know if A is divisible by B or not. We don't need the remainder.
If we change our expectations to only get back true or false from this type, we're actually there. If we change the name to IsDivisible, and change our returns, we're there:
type IsDivisible<A extends number, B extends number, Accumulator extends number = B> =
A extends Accumulator
? true
: LessThan<A, Accumulator> extends true
? false
: IsDivisible<A, B, Add<Accumulator + B>>
Lets try it out:
We can now build two new types for IsDivisibleBy3 and IsDivisibleBy5:
type IsDivisibleBy3<A extends number> = IsDivisible<A, 3>
type IsDivisibleBy5<A extends number> = IsDivisible<A, 5>
An aside, this is kinda like partial application, if that's your thing.
We can now go back to the initial problem, FizzBuzz. We had this type:
type FizzBuzz<N extends number> = IsDivisibleBy3<N> extends true ???
And we now have that type!
Lets return Fizz if its divisible by 3, otherwise return the number passed in:
type FizzBuzz<N extends number> = IsDivisibleBy3<N> extends true
? "Fizz"
: N
Now we can add the IfDivisibleBy5 in as a nested conditional type:
type FizzBuzz<N extends number> = IsDivisibleBy3<N> extends true
? "Fizz"
: IfDivisibleBy5<N> extends true
? "Buzz"
: N
Now, the final piece of the puzzle. How do we do &&
IsDivisibleB y3<N> && IfDivisibleBy5<N>
Well, we build a type of course:
We can build a type called And that takes two generics, and returns true if both of them evaluate to true, and false otherwise:
type And<A extends boolean, B extends boolean> = A extends true
? B extends true
? true
: false
: false
So if A and B are true, we return true, if A is true but B is false we return false, and if A is false, we return false
We can use that type like:
type FizzBuzz<N extends number> = And<IsDivisibleBy3<N>, IfDivisibleBy5<N>>
? "FizzBuzz"
: IsDivisibleBy3<N> extends true
? "Fizz"
: IfDivisibleBy5<N> extends true
? "Buzz"
: N
And that should be a working type, it takes a number and returns either FizzBuzz, Fizz, Buzz or the number passed in.
Now, the final final puzzle piece, we want to build a type that can generate a tuple of FizzBuzzes over a range. For example:
FizzBuzzRange<1, 10>
Should generate the tuple:
// [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz"]
This should be quite simple using everything we have learnt so far:
We know we're going to use recursion to build up the tuple, so we know we'll take a third param in our type that eventually will get returned:
type FizzBuzzRange<
A extends number,
B extends number,
Tuple extends unknown[] = []
> = ???
Now if we add one to A each time through our "loop" and return when A is equal to B, we'd have the base case of our recursion:
type FizzBuzzRange<
A extends number,
B extends number,
Tuple extends unknown[] = []
> = A extends B
? Tuple
: ???
If A doesn't equal B, then we need to recursivly call our type again, adding the result of calling FizzBuzz to our tuple, and also incrementing A by one:
type FizzBuzzRange<
A extends number,
B extends number,
Tuple extends unknown[] = []
> = A extends B
? Tuple
: FizzBuzzRange<Add<A, 1>, B, [...Tuple, FizzBuzz<A>]>
And that's it, FizzBuzz built completely in the type system, zero run time typescript. Beautiful, but please never do this.
]]>The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00533-easy-concat/README.md
Coming soon
For example, given the following cases, our Concat type should return the type shown in the comments:
Concat<[], []> // []
Concat<[], [1]> // [1]
Concat<[1, 2], [3, 4]> // [1, 2, 3, 4]
Concat<['1', 2, '3'], [false, boolean, '4']>// ['1', 2, '3', false, boolean, '4']
If we were to solve this challenge with modern runtime javascript, the solution would be very simple:
function concat(t, u) {
return [...t, ...u];
}
We'd create a function that accepts two parameters, then we'd return a new array containing everything in t and u, by spreading both arrays.
We can do this exact thing in the type system using Variadic Tuple Types!
We'll start by implementing a type that accepts two arrays as generics:
type Concat<T extends unknown[], U extends unknown[]> = ??
We'll then use the spread operator to return a new type with all elements from T and U inside:
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
]]>
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/03057-easy-push/README.md
Coming soon
For example, given the following cases, our Push type should return the type shown in the comments:
Push<[], 1> // [1]
Push<[1, 2], '3'> // [1, 2, '3']
Push<['1', 2, '3'], boolean> // ['1', 2, '3', boolean]
This challenge is very similar to the Concat type challenge. So again, if we were to solve this challenge with modern runtime javascript without using the built in push methods, the solution would be very simple:
function push(t, u) {
return [...t, u];
}
We'd create a function that accepts two parameters, then we'd return a new array containing everything in a, followed by b, by spreading the contents of a.
We can do this exact thing in the type system using Variadic Tuple Types!
We'll start by implementing a type that accepts an array and an any:
type Push<T extends unknown[], U> = ??
We'll then use the spread operator to return a new type with all elements from T, followed by U:
type Push<T extends unknown[], U> = [...T, U]
]]>
The challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/03060-easy-unshift/README.md
Coming soon
For example, given the following cases, our Unshift type should return the type shown in the comments:
Unshift<[], 1> // [1]
Unshift<[1, 2], 0> // [0, 1, 2]
Unshift<['1', 2, '3'], boolean> // [boolean, '1', 2, '3']
This challenge is very similar to the Push challenge and Concat challenge. So again, if we were to solve this challenge with modern runtime javascript without using the built in unshift methods, the solution would be very simple:
function unshift(t, u) {
return [u, ...t];
}
We'd create a function that accepts two parameters, then we'd return a new array containing u, followed by everything in a, by spreading the contents of a.
We can do this exact thing in the type system using Variadic Tuple Types!
We'll start by implementing a type that accepts an array and an any:
type Unshift<T extends unknown[], U> = ??
We'll then use the spread operator to return a new type with U, followed by all elements from T:
type Unshift<T extends unknown[], U> = [U, ...T]
]]>