TypeScript 5 Mastery Guide 2026 — Advanced Types, Decorators, and Best Practices
Advertisement
Introduction
Why This Matters
TypeScript 5 closes the gap between type safety and developer ergonomics. Features like const type parameters, the satisfies operator, and variadic tuple types let you express complex constraints that were previously impossible without sacrificing usability.
const Type Parameters
Infer literal types without as const at every call site:
// Before TypeScript 5:
function createRoute<T extends string>(path: T) {
return { path, handler: () => {} }
}
const route = createRoute('/users')
// route.path is string, not '/users'
// TypeScript 5: const type parameter
function createRoute<const T extends string>(path: T) {
return { path, handler: () => {} }
}
const route = createRoute('/users')
// route.path is '/users' — literal type preserved!
// Especially powerful with objects:
function makeConfig<const T extends Record<string, unknown>>(config: T): T {
return config
}
const config = makeConfig({
port: 3000,
env: 'production',
features: ['auth', 'payments'],
})
// config.env is 'production', config.features is readonly ['auth', 'payments']The satisfies Operator
Validate a value matches a type while keeping the inferred literal type:
type Route = {
path: string
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
handler: () => Response
}
// Without satisfies: either loses literal types OR loses validation
const routes = {
getUser: {
path: '/users/:id',
method: 'GET',
handler: () => new Response(),
},
} satisfies Record<string, Route>
// routes.getUser.method is 'GET' (not string)
// TypeScript still validates the structure
// Real-world: config objects
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>
palette.red.map(v => v * 2) // OK — inferred as number[]
palette.green.toUpperCase() // OK — inferred as stringTemplate Literal Types
Build complex string types programmatically:
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type ApiVersion = 'v1' | 'v2'
type Endpoint = 'users' | 'posts' | 'comments'
type ApiRoute = `/${ApiVersion}/${Endpoint}`
// '/v1/users' | '/v1/posts' | ... | '/v2/comments'
// Extracting parts of a string type
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never
type Params = ExtractParams<'/users/:id/posts/:postId'>
// 'id' | 'postId'
// Event name generation
type EventMap<T extends string> = {
[K in T as `on${Capitalize<K>}`]: (event: Event) => void
}
type ButtonEvents = EventMap<'click' | 'hover' | 'focus'>
// { onClick: ..., onHover: ..., onFocus: ... }Decorators (Stage 3)
TypeScript 5 supports the TC39 Stage 3 decorators spec:
// Method decorator for logging
function log(target: any, context: ClassMethodDecoratorContext) {
const methodName = String(context.name)
return function (this: any, ...args: any[]) {
console.log(`Calling ${methodName} with`, args)
const result = target.call(this, ...args)
console.log(`${methodName} returned`, result)
return result
}
}
// Property decorator for validation
function required(target: undefined, context: ClassFieldDecoratorContext) {
return function (this: any, value: any) {
if (value === undefined || value === null) {
throw new Error(`${String(context.name)} is required`)
}
return value
}
}
class UserService {
@required
private apiKey: string
constructor(apiKey: string) {
this.apiKey = apiKey
}
@log
async getUser(id: string) {
const res = await fetch(`/api/users/${id}`, {
headers: { Authorization: `Bearer ${this.apiKey}` },
})
return res.json()
}
}Variadic Tuple Types and Inference
// Concat two tuples with preserved types
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
type AB = Concat<[string, number], [boolean, Date]>
// [string, number, boolean, Date]
// Strongly typed pipe function
function pipe<A, B, C>(
fn1: (a: A) => B,
fn2: (b: B) => C
): (a: A) => C {
return (a) => fn2(fn1(a))
}
const parseAndDouble = pipe(
(s: string) => parseInt(s, 10),
(n: number) => n * 2
)
const result = parseAndDouble('21') // result: number — inferred correctlyCommon Mistakes
- Using
anyinstead ofunknownfor values from external APIs —unknownforces type checking - Writing
as Typecasts instead of usingsatisfiesor proper type guards - Defining overly wide types like
objectorRecord<string, any>when a specific shape is known - Not using
constassertions orconsttype parameters for configuration objects - Ignoring
strictNullChecks— enabling it reveals real bugs in most codebases
Best Practices
- Enable
strict: trueintsconfig.jsonfrom day one - Use
satisfieswhen you want validation without losing inferred literal types - Prefer
unknownoveranyfor external data, then narrow with type guards or Zod - Use
consttype parameters for functions that receive configuration objects - Write utility types with template literals to derive event maps, route params, and API types
Key Takeaways
consttype parameters preserve literal types in generic functions without requiringas constat call sites- The
satisfiesoperator validates structure while keeping the narrower inferred type - Template literal types can derive complex union types from string patterns
- TypeScript 5 implements TC39 Stage 3 decorators — different API from the legacy
experimentalDecorators - Variadic tuple types (
[...T, ...U]) enable type-safe pipe and compose utilities unknownis always preferable toanyfor external data — it forces explicit narrowingstrictNullChecksandstrict: truecatch the most bugs and should be enabled from project start- Use Zod or Valibot with TypeScript for runtime validation that stays in sync with your static types
Advertisement
Related reading
TypeScript 5.x Features Every Backend Developer Must Use6 min readGoogle Gemini API Guide 2026 — Build AI Apps with Gemini 2.0 Flash5 min readBuild an AI Chatbot with Next.js 15 and OpenAI — Full Stack 20266 min readCursor AI — Advanced Tips and Tricks for 20267 min readJavaScript Array Methods — The Complete Cheatsheet for 20266 min readJavaScript Async/Await — Stop Writing Callback Hell5 min read