## DEV Community

Prithpal Sooriya

Posted on • Updated on

# FizzBuzz... but only using TypeScript Types

Typescript 4.1 has released some awesome new features, including Template Literals - which enable some really powerful stuff!

To try it out, I've done the generic FizzBuzz test only using Typescript Types!

# ts-fizz-buzz

The generic Fizz Buzz test built entirely with TypeScript type annotations.
This means that is works solely on typescript types - so it is a compile time Fizz Buzz "Solution".
And using VSCode IntelliSense you see the solution without even "running" your code!

## See a live demo

This post is a breakdown/explanation of the types created & used in the Repo.

## Outline & Plan.

FizzBuzz is a basic programming task that is (was?) used in interviews.
For those who've never seen the Fizz Buzz problem here it is:

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.

Before we delve into the solution, I think its worth breaking down the tasks we need to accomplish:

1. Build our Predicate Types. We need a way to see if a number is divisible by 3 or 5.
2. Combine our Predicate Types & solve FizzBuzz for a single number.
3. Solve FizzBuzz for an array of numbers.

## Predicate Types - check the divisibility

We want to check if a number is divisible by 3 or 5, but sadly TypeScript (currently) does not support arithmetic operations - e.g. we can't use a modulus to see if a number is divisible.

But TS Template Literals now allow us to build some of the divisibility rules!

``````// 5 Divisibility rule, we only need to look at last digit & see if it is a 0 or 5.
export type IsDivisibleBy5<T extends number> =
`\${T}` extends `\${infer OtherDigits}\${0 | 5}` ? true : false;
``````

Breakdown:

• `<T extends number>` - This enforces users to only input numbers.
• ``\${T}`` - The back ticks are the new template literal syntax, this allows us to convert the number input into a string.
• ``\${infer OtherDigits}...`` - the infer acts like a wildcard that is (in this case) a string without the final character.
• ``\${0 | 5} ? true : false` - checks if the last number is 0 or a 5. It if is, then the Type resolves to `true` otherwise `false`.

Sadly I couldn't generate the number 3 divisibility rule (since it includes adding each digit, which requires an arithmetic operation), so stuck with a simple union of valid numbers.

``````// Unsure if it is possible to generate a 3 Divisibility rule using current version of Typescript
type Finite3Tuple = [
0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33,
36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66,
69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99
]
export type IsDivisibleBy3<T extends number> =
T extends Finite3Tuple[number] ? true : false;
``````

## Fizz Buzz for a single number

``````// This is to remove the number at end of recursive concatenation in FizzBuzz below.
// E.g. Fizz4, Fizz2, ...
type RemoveTrailingNumber<Str extends string | number, T extends number> =
`\${Str}` extends `\${infer Rest}\${T}` ? Rest : Str

// Helper type to abstract the recursion & conversion
type ConcatBuzz<T extends number, BuzzPredicate> = RemoveTrailingNumber<FizzBuzz<T, null, BuzzPredicate>, T>

/* Fizz Buzz type used for single number */
export type FizzBuzz<
T extends number,
FizzPredicate = IsDivisibleBy3<T>,
BuzzPredicate = IsDivisibleBy5<T>
> =
FizzPredicate extends true ? `Fizz\${ConcatBuzz<T, BuzzPredicate>}` :
BuzzPredicate extends true ? `Buzz` :
T
``````

The idea behind it is that it will recursively build the FizzBuzz string. Whenever the `Fizz` is found, we can then combine the `Buzz` onto it (if found). To prevent the Fizz/Buzz from continuously being concatenated, we set their predicate to `null`.

Lets focus on the Main FizzBuzz type, the other types are just helpers to abstract out the recursion & trimming the output.

• It takes 3 arguments/generics. `T extends number` which is the input; and the Fizz & Buzz Predicates (`FizzPredicate` & `BuzzPredicate`).
• If `FizzPredicate` is valid, then we start building our string ``Fizz\${ConcatBuzz<T, BuzzPredicate>}``.

• The `ConcatBuzz` is just a helper type to abstract away the recursion. It recalls `FizzBuzz`, but sets the `FizzPredicate` to `null`, so the remaining iterations go through the rest of the predicates.
• If `BuzzPredicate` is valid, then we just return `Buzz`.

• Finally, if none of the predicates are valid then we just return the number.

## Fizz Buzz for an array

``````type CalculateFizzBuzz<T extends number> = FizzBuzz<T, IsDivisibleBy3<T>, IsDivisibleBy5<T>>

export type FizzBuzzArray<Arr extends number[]> = {
[K in keyof Arr]: CalculateFizzBuzz<Arr[K] & number>
}
``````

`CalculateFizzBuzz` is just a helper type that allows us to simplify `FizzBuzzArray` type.

Breakdown of `FizzBuzzArray`:

• `Arr extends number[]` - enforces that the type must take a number array. This will be our input.

• `{ [K in keyof Arr]: ... }` - Arrays can be treated as objects where the keys are the indices. So we can use the `keyof` to go through each key/index.

• `CalculateFizzBuzz<Arr[K] & number>` - This runs FizzBuzz for this value with the given index. The `& number` is the TS intersection type - we can use it to enforce that the given value is a number.

• Similarly, we could have used a ternary as well: `Arr[K] extends number ? CalculateFizzBuzz<Arr[K]> : never`

Thats it!
Now if we give it an input array, we get the FizzBuzz result out of it!

Check out the TS playground in the Github repo for more (& for an extended version of FizzBuzz).