Sitemap

Higher Order Type in TypeScript

3 min readApr 17, 2025
TypeScript’s type system is a fully fledged programming language — generated by Gemini 2.0

Scope

In the present article, we are going to implement a type alias RGB, so that a color has to be made of a triplet of strings representing numbers from 0 up to 255.

const turquoise: RGB = ['64', '224', '208'];

Step 1: Span

Let’s start off by writing a utility type which generates an array type containing the numbers from 0 up to N-1.

type Span<N extends number, Acc extends unknown[]=[]> =
N extends Acc['length'] ?
Acc :
Span<N, [...Acc, Acc['length']]>;

type SpanArray = Span<256>; // [0, 1, 2, 3, ..., 255]
type SpanUnion= SpanArray[number]; // 0 | 1 | 2 | 3 | ... | 255

Step 2: Mapping

So far so good. Looking back at the requirement, we need an union of strings representing numbers and not an union of numbers.

For that, let’s use the template literal type to do a type transformation.

type Span<N extends number, Acc extends unknown[]=[]> =
N extends Acc['length'] ?
Acc :
Span<N, [...Acc, `${Acc['length']}`]>;

type SpanArray = Span<256>; // ['0', '1', '2', '3', ..., '255']
type SpanUnion = SpanArray[number]; // '0' | '1' | '2' | '3' | ... | '255'

Step 3: Fine-tuning

The requirements are met at this point.

However, we’d like to decouple the transformation from the iteration. For that, we would like to pass a utility type to Span. That’s where Higher Order Type — aka HOT — comes into play.

A HOT is a utility type accepting another another utility type as parameter. HOTs are not natively supported in TS. Let’s see a technique to simulate them.

First, let’s define an interface which any transformation could be based on.

interface Fn {
input: unknown;
output: unknown;
}

Now, let’s create the utility type Apply, which takes an Fn, overrides its input type and returns its output.

type Apply<F extends Fn, X> = (F & { input: X })['output'];

Finally, let’s implement the transformation, which turns a number type into a string type.

interface Number2String extends Fn {
output: this['input'] extends infer I extends number ? `${I}` : never;
}

Step 4: Put all pieces together

interface Fn {
input: unknown;
output: unknown;
}

type Apply<F extends Fn, X> = (F & { input: X })['output'];

interface Number2String extends Fn {
output: this['input'] extends infer I extends number ? `${I}` : never;
}

interface Id extends Fn {
output: this['input'];
}

type Span<N extends number, T extends Fn=Id, Acc extends unknown[]=[]> =
N extends Acc['length'] ?
Acc :
Span<N, T, [...Acc, Apply<T, Acc['length']>]>;


type SpanArray = Span<256, Number2String>; // ['0', '1', '2', ..., '255']
type SpanUnion = SpanArray[number]; // '0' | '1' | '2' | ... | '255'
type RGB = [SpanUnion, SpanUnion, SpanUnion];

const turquoise :RGB = ['64', '224', '208'];
const salmon :RGB = ['250', '128', '114'];
const fuchsia :RGB = ['255', '0', '255'];

Wrap up

HOT is not a built-in construct in TypeScript but we showed a technique used in the TypeScript’s ecosystem to simulate it.

--

--

Thomas Rubattel
Thomas Rubattel

Written by Thomas Rubattel

Front-end developer based in Tokyo and Zürich

Responses (1)