Type-Level Wordle in TypeScript
- Published On
- Read Time
- 4 min
After type-level Hangman, here’s the next game: Wordle, implemented in TypeScript at the type level. Your guessed words are represented as a type, and the result is also a type. There’s no runtime code - just pure type manipulation.

The colors work like in the real game: 🟩 means the letter is in the right spot, 🟨 means it’s in the word but in the wrong spot, and ⬜️ means it’s not in the word at all.
How many languages let you do this? TypeScript is impressive!
Implementation
Here’s the full code:
/* ------------------------------------------------------------ *\
WORDLE IN TYPESCRIPT TYPES
\* ------------------------------------------------------------ */
type Input = [
// Guess up to five times
];
// ⬇️ Hover over the type to see the result
type Result = Run<Input>;
// ---------------------------------------------------------------
//
//
//
// DO NOT CHEAT!
//
//
//
// ---------------------------------------------------------------
type Run<T> = T extends ValidInput ? Guess<TakeUntil<T, GameSolution>> : "Invalid Input";
type Guess<T extends ValidInput> = ObjectFromArray<{
[K in keyof T]: GuessWord<Uppercase<T[K]>>;
}>;
type GuessWord<T extends string> = T extends ValidWord
? `${T}: ${ReplaceWithEmojis<
Breath<ActualWordOf<ReplacePartialAndNonMatches<ReplaceMatches<[T, GameSolution]>>>>
>}${T extends GameSolution ? " 🥳" : ""}`
: `${T}: Unknown Word`;
type ReplaceMatches<T extends WordTuple> = T extends [
`${infer ActualLetter}${infer ActualRest}`,
`${infer ExpectedLetter}${infer ExpectedRest}`,
]
? ActualLetter extends ExpectedLetter
? Prefix<ReplaceMatches<[ActualRest, ExpectedRest]>, Match>
: Prefix<ReplaceMatches<[ActualRest, ExpectedRest]>, ActualLetter, ExpectedLetter>
: ["", ""];
type ReplacePartialAndNonMatches<T extends WordTuple> = T extends [
`${infer ActualLetter}${infer ActualRest}`,
infer Expected extends string,
]
? ActualLetter extends Match
? Prefix<ReplacePartialAndNonMatches<[ActualRest, Expected]>, ActualLetter, "">
: Contains<ActualLetter, Expected> extends true
? Prefix<ReplacePartialAndNonMatches<[ActualRest, ReplaceFirst<ActualLetter, Expected>]>, PartialMatch, "">
: Prefix<ReplacePartialAndNonMatches<[ActualRest, Expected]>, NoMatch, "">
: T;
/* ------------------------------------------------------------ *\
Input
\* ------------------------------------------------------------ */
type ValidInput = OptionalTuple<[string, string, string, string, string]>;
/* ------------------------------------------------------------ *\
Word Tuple
\* ------------------------------------------------------------ */
type WordTuple = [actual: string, expected: string];
type ActualWordOf<T extends WordTuple> = T[0];
/**
* Prefixes the tuple elements with the given prefix.
*/
type Prefix<T extends WordTuple, With extends string, WithExpected extends string = With> = [
`${With}${T[0]}`,
`${WithExpected}${T[1]}`,
];
/* ------------------------------------------------------------ *\
Match types
\* ------------------------------------------------------------ */
type ValidWord = WordList[number];
type ValidateWord<T> = T extends ValidWord ? T : { error: "Invalid solution" };
// We use single code point characters while matching and map them later.
// Emojis would break `extends` checks.
type Match = "+";
type PartialMatch = "~";
type NoMatch = "-";
type EmojiMap = { "+": "🟩"; "~": "🟨"; "-": "⬜️" };
/**
* Replaces the matching characters in T with emojis.
* Every unknown character is kept as is.
*/
type ReplaceWithEmojis<T extends string> = T extends `${infer Char}${infer Rest}`
? `${Char extends keyof EmojiMap ? EmojiMap[Char] : Char}${ReplaceWithEmojis<Rest>}`
: T;
/* ------------------------------------------------------------ *\
Utils
\* ------------------------------------------------------------ */
/**
* Adds white-spaces between each character of `T`.
*/
type Breath<T extends string> = T extends `${infer A}${infer B}` ? (B extends "" ? A : `${A} ${Breath<B>}`) : T;
/**
* Converts the array `T` into an object.
* Unlike arrays, objects are rendered multi-line in the hover preview in VS Code.
*/
type ObjectFromArray<T> = {
[K in keyof T as K extends `${number}` ? K : never]: T[K];
};
/**
* Checks if `Find` is within `Word`.
*/
type Contains<Find extends string, Word extends string> = Word extends `${string}${Find}${string}` ? true : false;
/**
* Replaces the first occurrence of the character `Find` in `Word` with `With`.
*/
type ReplaceFirst<Find, Word, With extends string = "_"> = Word extends `${infer Letter}${infer Rest}`
? Find extends Letter
? `${With}${Rest}`
: `${Letter}${ReplaceFirst<Find, Rest, With>}`
: Word;
/**
* Converts the tuple `T` into a union type from [] to `T`.
* @example `OptionalTuple<[x, x, x]> === [] | [x] | [x, x] | [x, x, x]`
*/
type OptionalTuple<T extends unknown[]> = T extends [unknown, ...infer Rest] ? T | OptionalTuple<Rest> : T;
type TakeUntil<T extends ValidInput, Match> = T extends [infer Word, ...infer Rest extends ValidInput]
? Word extends Match
? [Word]
: [Word, ...TakeUntil<Rest, Match>]
: T;
/* ------------------------------------------------------------ *\
Content
\* ------------------------------------------------------------ */
type WordList = [
"CLASS",
"CONST",
"WHILE",
"YIELD",
"ASYNC",
"AWAIT",
"THROW",
"CATCH",
"FINAL",
"SUPER",
"RAISE",
"MATCH",
"FALSE",
"BEGIN",
"UNTIL",
"DEFER",
"MACRO",
"TRAIT",
"MIXIN",
"PANIC",
"EMPTY",
"ISSET",
"UNSET",
"ARROW",
"APPLY",
"TYPES",
"FLOAT",
"SHORT",
"ARRAY",
"SLICE",
"TUPLE",
"ENUMS",
"UNION",
"BYTES",
"CHARS",
"STACK",
"QUEUE",
"HEAPS",
"GRAPH",
"NODES",
"EDGES",
"INDEX",
"VALUE",
"MODEL",
"TABLE",
"FIELD",
"PARSE",
"PRINT",
"INPUT",
"FETCH",
"MERGE",
"SPLIT",
"EVERY",
"FLOOR",
"ROUND",
"POWER",
"SHIFT",
"SPAWN",
"FLUSH",
"MOUNT",
"BATCH",
"TRACE",
"DEBUG",
"CLONE",
"STASH",
"PATCH",
"RESET",
"BLAME",
"BLOBS",
"FORKS",
"BUILD",
"STAGE",
"REACT",
"REDUX",
"REMIX",
"ASTRO",
"MAVEN",
"NGINX",
"REDIS",
"FLASK",
"BABEL",
"CMAKE",
"CARGO",
"NUGET",
"KAFKA",
"SPARK",
"CONDA",
"LINUX",
"REGEX",
"SWIFT",
"SCALA",
"OCAML",
"COBOL",
"BASIC",
"MYSQL",
"MONGO",
"QUERY",
"JOINS",
"VIEWS",
"CACHE",
"TOKEN",
"DELTA",
"QUOTA",
"CHUNK",
"FRAME",
"LAYER",
"PROXY",
"ROUTE",
"SCOPE",
"BLOCK",
"STORE",
"STATE",
"PROPS",
"HOOKS",
"FORMS",
"COLOR",
"HOVER",
"WIDTH",
"ALIGN",
"LABEL",
"IMAGE",
"HTTPS",
"LINKS",
"CHMOD",
"MKDIR",
"TOUCH",
"WHICH",
"SLEEP",
"CLEAR",
"ALIAS",
"SHELL",
"HOSTS",
"PORTS",
"PATHS",
"GLOBS",
"CRONS",
"MUTEX",
"LOGIC",
"ERROR",
"FATAL",
"TESTS",
"MOCKS",
"SPECS",
"LINTS",
"OCTAL",
"LOOPS",
"FLAGS",
"SOCKS",
];
type GameSolution = ValidateWord<"TYPES">;