0Pricing
TypeScript Academy · Lesson

The HKT Problem in TypeScript

Understand why generic type constructors are hard.

The HKT Problem in TypeScript is a free TypeScript Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is a Higher-Kinded Type

A higher-kinded type (HKT) abstracts not over a concrete type, but over a type constructor: something like Array or Promise that needs an argument to become a real type.

Array alone is not a type. Array<string> is. Array is a function on types, and HKTs let us be generic over such functions.

type StringArray = Array<string>;
type NumberArray = Array<number>;
// Array by itself is a type constructor, not a type

Kinds, Briefly

Just as values have types, type constructors have kinds. A plain type like number has kind *. Array has kind * -> *: give it one type, get a type back. Being generic over these is "higher-kinded".

type ValueLike = number;        // kind *
// Array needs one argument:    kind * -> *
// Map needs two arguments:     kind * -> * -> *

The Goal: Functor<F>

In languages with HKT support you can write a single Functor<F> abstraction with one map that works for any container F, whether it is an array, an option, or a promise. One interface, many containers.

interface Wish_Functor<F> {
  map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
}
// F<A> here is the wish - and TypeScript rejects it

The Problem

TypeScript does not support applying a type parameter to an argument. You cannot write F<A> when F is a generic parameter. The compiler errors with "Type F is not generic".

// interface Functor<F> {
//   map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
// }
// Error: Type F is not generic

Why TS Lacks It

TypeScript type parameters are first-order: they range over concrete types, not over type constructors. There is no syntax to say "F is something that takes one type argument". So F<A> is meaningless to the compiler.

// You can pass number, string, User as F...
// but not Array, not Promise (the constructors themselves)

A Concrete Pain Point

Without HKTs you must duplicate map for every container: one for arrays, one for options, one for promises. The logic is identical but the type system cannot unify them.

declare function mapArray<A, B>(fa: A[], f: (a: A) => B): B[];
declare function mapOption<A, B>(fa: A | null, f: (a: A) => B): B | null;
// Same shape, but no way to abstract over the container

What We Want to Express

We want one signature: given a container of A and a function A -> B, return a container of B, preserving the container type. Array stays array; option stays option.

// Wish: map<F, A, B>(fa: F<A>, f: (a: A) => B): F<B>
// where F is preserved across the call

Other Languages

Haskell and Scala have HKTs natively, which is why type classes like Functor, Applicative, and Monad are expressible there. TypeScript needs a workaround to approximate the same power.

// Haskell: class Functor f where fmap :: (a -> b) -> f a -> f b
// TypeScript: needs an encoding trick (next lessons)

The Encoding Idea

The standard workaround is defunctionalization: instead of applying F directly, we give each container a string tag (a URI) and keep a registry that maps the tag plus an argument to the concrete type. Type application becomes a lookup.

// Instead of F<A>, we write Kind<F, A> where F is a URI string
// and a registry resolves it to the real type

What You Will Build

Over the next lessons you build the fp-ts style HKT encoding step by step: a URItoKind registry, a Kind lookup, and finally a generic Functor whose map works for any registered container.

// Roadmap:
// 1. Type constructors via URI strings
// 2. Kind<F, A> resolution
// 3. Generic Functor + instances

Why It Is Worth Knowing

You rarely write this encoding yourself, but libraries like fp-ts and Effect rely on it. Understanding the trick lets you read their types, write generic abstractions over containers, and appreciate what the type system can and cannot do.

// Recognizing Kind<F, A> in library code is the payoff

Quick Check

Test your understanding of the HKT problem.

Recap

You met the higher-kinded type problem.

  • HKTs abstract over type constructors like Array, not concrete types.
  • TypeScript parameters are first-order, so F<A> is illegal.
  • This blocks a single generic Functor with one map.
  • The fix is an encoding using URI tags and a registry.

Next: defining type constructors via URI strings.

Frequently asked questions

Is the “The HKT Problem in TypeScript” lesson free?

Yes — the full text of “The HKT Problem in TypeScript” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “The HKT Problem in TypeScript”?

Understand why generic type constructors are hard. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The HKT Problem in TypeScript” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The HKT Problem in TypeScript
  2. Defining Type Constructors
  3. The Lightweight HKT Pattern
  4. Generic Functors and Mappers
← Back to TypeScript Academy