Introduction
Introduced in TypeScript 5.0, const type parameters allow you to infer literal types from arguments without requiring callers to use as const. This feature makes generic functions more precise and easier to use, particularly when building APIs that work with configuration objects and fixed sets of values.
Key Concepts
- Const Type Parameter (
const T): A type parameter prefixed withconstthat infers the narrowest (literal) type from the argument. - Literal Inference: Without
const, TypeScript widens"hello"tostring. Withconst, it stays as"hello". - Immutable Inference: Const type parameters also make inferred arrays and objects readonly.
Real World Context
When building type-safe builders, routers, or event systems, you want the inferred types to be as precise as possible. Before TypeScript 5.0, callers had to write as const at every call site. Const type parameters move this responsibility to the function author, making the API simpler for consumers.
Deep Dive
The Problem: Widening
Without const, TypeScript widens literal types:
typescriptfunction createRoute<T extends string>(path: T): { path: T } { return { path }; } const route = createRoute("/dashboard"); // route.path is string — not "/dashboard"
TypeScript widens "/dashboard" to string because T extends string allows any string. The specific path is lost.
The Solution: const Type Parameter
Prefixing the type parameter with const preserves the literal:
typescriptfunction createRoute<const T extends string>(path: T): { path: T } { return { path }; } const route = createRoute("/dashboard"); // route.path is "/dashboard" — literal type preserved!
The const modifier tells TypeScript to infer the narrowest possible type, just as if the caller had written as const.
With Objects and Arrays
Const type parameters work with complex types too:
typescriptfunction defineConfig<const T extends Record<string, unknown>>(config: T): T { return config; } const config = defineConfig({ port: 3000, host: "localhost", features: ["auth", "logging"], }); // config.port is 3000 (literal), not number // config.host is "localhost" (literal), not string // config.features is readonly ["auth", "logging"] — narrowed and readonly
The entire structure gets literal inference, including nested arrays becoming readonly tuples.
Practical Example: Type-Safe Event Emitter
typescriptfunction on<const TEvent extends string>( event: TEvent, handler: (event: TEvent) => void ): void { // Register handler... } on("click", (event) => { // event is "click" (literal), not string console.log(`Handling: ${event}`); });
The handler receives the exact event name as a literal type, enabling powerful type-level patterns like event-specific payloads.
Before vs After
The improvement is about developer experience:
typescript// Before TypeScript 5.0 — caller must use 'as const' const route = createRoute("/dashboard" as const); // After TypeScript 5.0 — function handles it const route = createRoute("/dashboard"); // Just works
The as const burden moves from every call site to the single function definition.
Common Pitfalls
- Using
constwhen widening is desired — If you wantTto bestring(not a literal), do not useconst. It is only appropriate when you need the specific value type. - Forgetting that arrays become readonly — With
consttype parameters, inferred arrays arereadonly. If the function modifies the array, this causes type errors.
Best Practices
- Use
consttype parameters in builder/factory functions — Any function that creates configuration, routes, or schemas benefits from literal inference. - Document the literal inference behavior — Since this is a newer feature, add JSDoc comments explaining that the function preserves literal types.
Summary
consttype parameters (TypeScript 5.0+) infer literal types without requiringas constat the call site.- They make generic APIs more precise and easier to use.
- Arrays inferred through const type parameters become readonly.
- Use them in builder functions, configuration factories, and event systems.
Code Examples
// const type parameter preserves literal types automatically
function createEndpoint<const T extends { method: string; path: string }>(
config: T
): T {
return config;
}
const endpoint = createEndpoint({
method: "GET",
path: "/api/users",
});
// endpoint.method is "GET" (literal), not string
// endpoint.path is "/api/users" (literal), not string
// No 'as const' needed at the call site!