TypeScript 5.5: satisfies, const, using in 2026
TypeScript 5.5: satisfies, const, and using in 2026
TL;DR
TypeScript 5.x releases through 2024–2025 shipped features that fundamentally change how TypeScript developers write type-safe code. The four you need to know: satisfies (validate a value against a type without widening it), const type parameters (infer literal types in generics), using / await using (resource management via the Explicit Resource Management proposal), and inferred type predicates (TypeScript now infers x is Type from function return types). These aren't niche features — they're in daily use patterns for anyone writing production TypeScript in 2026.
Key Takeaways
satisfiessolves the "I want validation but not widening" problem —config satisfies Configchecks the type but keeps the inferred literal types instead of widening toConfigconsttype parameters enable generics that infer["a", "b"]instead ofstring[]— finally fixes a major pain point for builder patterns and route definitionsusingimplements TC39 Explicit Resource Management — automatic cleanup of database connections, file handles, and any disposable resource, guaranteed even if an exception is thrown- TypeScript infers type predicates since 5.5 —
arr.filter(x => x !== null)is now typed asNonNullable<T>[]without manual annotation - TypeScript downloads on npm: ~60M weekly downloads, the most-downloaded programming language toolchain in the npm ecosystem
usingis not yet universally adopted in libraries — check that your database driver and HTTP client expose a Symbol.dispose interface before relying on it
satisfies — Validation Without Widening
The Problem Before satisfies
Before TypeScript 4.9, you had two options for type-checking object literals:
type Route = { path: string; component: string }
// Option 1: Type annotation — validates, but widens
const routes: Route[] = [
{ path: '/home', component: 'Home' },
]
// routes[0].path is string (widened) — autocomplete shows all string methods
// You can't do routes[0].path.toUpperCase() and get 'HOME' — it's just string
// Option 2: No annotation — keeps literal types, but no validation
const routes = [
{ path: '/home', component: 'Home' },
]
// routes[0].path is '/home' (literal) — but TypeScript won't catch typos in keys
You wanted validation and literal type inference. satisfies gives you both.
Using satisfies
type Route = {
path: string
component: string
protected?: boolean
}
const routes = [
{ path: '/home', component: 'Home' },
{ path: '/dashboard', component: 'Dashboard', protected: true },
] satisfies Route[]
// ✅ routes[0].path is '/home' (literal type preserved)
// ✅ TypeScript validates structure — typos in keys are errors
// ✅ routes[0].protected is boolean | undefined (narrowed)
routes[0].path // Type: '/home'
routes[1].protected // Type: true (literal!)
Real-World satisfies Patterns
Configuration objects with discriminated unions:
type DbConfig =
| { driver: 'postgres'; connectionString: string }
| { driver: 'sqlite'; filename: string }
const config = {
driver: 'postgres',
connectionString: process.env.DATABASE_URL!,
} satisfies DbConfig
// config.driver is 'postgres' (literal), not 'postgres' | 'sqlite'
// TypeScript won't let you add connectionString if driver is 'sqlite'
Validated route maps with literal key types:
const routes = {
home: '/home',
dashboard: '/dashboard',
settings: '/settings',
} satisfies Record<string, `/${string}`>
// routes.home is '/home' — template literal type preserved
// TypeScript validates every value matches `/${string}` — no bare 'home' allowed
const Type Parameters — Infer Literal Types in Generics
The Problem
Before TypeScript 5.0, generics inferred widened types:
function makeArray<T>(values: T[]): T[] {
return values
}
const arr = makeArray(['a', 'b', 'c'])
// arr is string[] — literal types are lost
For builder patterns, route definitions, and tuple inference, this was a persistent pain:
function defineRoutes<T extends string>(paths: T[]): T[] {
return paths
}
const routes = defineRoutes(['/home', '/dashboard'])
// routes is string[] — should be ('/home' | '/dashboard')[]
The const Fix
function defineRoutes<const T extends string>(paths: T[]): T[] {
return paths
}
const routes = defineRoutes(['/home', '/dashboard'])
// routes is ('/home' | '/dashboard')[] ✅
// Now route-safe patterns work:
function navigate(route: typeof routes[number]) { ... }
navigate('/home') // ✅
navigate('/invalid') // ❌ TypeScript error
The const modifier on the type parameter tells TypeScript to infer the most specific type possible — treating the argument as if it were prefixed with as const.
Builder Pattern Example
This is transformative for fluent builder APIs:
class Router<const Routes extends string = never> {
private routes: Routes[] = []
add<const R extends string>(route: R): Router<Routes | R> {
this.routes.push(route as any)
return this as any
}
navigate(route: Routes) {
window.location.href = route
}
}
const router = new Router()
.add('/home')
.add('/dashboard')
.add('/settings')
router.navigate('/home') // ✅
router.navigate('/invalid') // ❌ TypeScript error
Before const type parameters, this required complex as const gymnastics. Now it works with straightforward type parameter syntax.
using and await using — Explicit Resource Management
The Problem with Manual Cleanup
Resource management in JavaScript has always been manual:
const db = await createConnection(url)
try {
const result = await db.query('SELECT ...')
await processResult(result)
} finally {
await db.close() // Must remember this
}
If you forget the finally block, connections leak. If an exception throws before you reach db.close(), the connection leaks. This pattern is repeated thousands of times across production codebases.
using — Synchronous Resource Management
The Explicit Resource Management TC39 proposal (Stage 4, implemented in TypeScript 5.2) introduces using:
function createTempDir() {
const dir = fs.mkdtempSync('/tmp/app-')
return {
path: dir,
[Symbol.dispose]() {
fs.rmSync(dir, { recursive: true })
}
}
}
function processFiles() {
using tmp = createTempDir()
// tmp.path is available here
fs.writeFileSync(`${tmp.path}/data.json`, JSON.stringify(data))
// processFiles(tmp.path)
// ✅ When this block exits (even via exception), Symbol.dispose() is called
// tmp directory is deleted automatically
}
await using — Async Resource Management
async function withDatabase<T>(
url: string,
callback: (db: Database) => Promise<T>
): Promise<T> {
await using db = await Database.connect(url)
// db has [Symbol.asyncDispose]() defined
return callback(db)
// ✅ db.close() called automatically, even if callback throws
}
// Usage
const result = await withDatabase(DATABASE_URL, async (db) => {
return db.query<User>('SELECT * FROM users WHERE id = $1', [userId])
})
For a resource to work with using, it needs a [Symbol.dispose]() method. For await using, it needs [Symbol.asyncDispose](). Libraries are gradually adopting these.
Which Libraries Support Symbol.dispose in 2026?
| Library | Symbol.dispose Support |
|---|---|
Node.js fs.promises.open() | ✅ (Node 22+) |
node:readline | ✅ (Node 22+) |
| Postgres.js | ⚠️ Partial (transaction context) |
| Drizzle ORM | ❌ (planned) |
| Prisma | ❌ (planned) |
| undici | ✅ |
| Web Locks API | ✅ |
| AbortController | ✅ (as of Node 22) |
You can always create a wrapper:
function disposable<T>(value: T, dispose: (v: T) => void | Promise<void>) {
return Object.assign(value, {
[Symbol.dispose]: () => dispose(value),
[Symbol.asyncDispose]: async () => dispose(value),
})
}
// Wrap any resource
using db = disposable(await createConnection(url), (conn) => conn.close())
Inferred Type Predicates (TypeScript 5.5)
This feature from TypeScript 5.5 (released June 2024) is small but eliminates a common boilerplate pattern.
Before: Manual Type Predicates
// Previously required manual annotation
function isString(value: unknown): value is string {
return typeof value === 'string'
}
const values: (string | null)[] = ['hello', null, 'world', null]
const strings = values.filter((v): v is string => v !== null)
// strings: string[]
After: Inferred Type Predicates
// TypeScript 5.5 infers the type predicate automatically
function isString(value: unknown) {
return typeof value === 'string'
}
// isString is inferred as (value: unknown) => value is string ✅
const values: (string | null)[] = ['hello', null, 'world', null]
const strings = values.filter(v => v !== null)
// strings: string[] ✅ — TypeScript infers the predicate from the callback
The filter case is the biggest win — arr.filter(x => x !== null) now correctly narrows the type without a manual (x): x is NonNullable<T> => x !== null annotation.
Other Notable TypeScript 5.x Features
TypeScript 5.0: const type parameters (above), decorators (stable, new syntax)
TypeScript 5.1: Easier implicit returns for undefined-returning functions, linked cursors in JSX editing
TypeScript 5.2: using / await using, Symbol.dispose, decorator metadata
TypeScript 5.3: import attribute narrowing, resolution-mode improvements
TypeScript 5.4: NoInfer<T> utility type (prevents type inference from a specific parameter)
TypeScript 5.5: Inferred type predicates, isolated declarations, --moduleResolution bundler stable
TypeScript 5.6+: Performance improvements in isolatedDeclarations mode, new tsconfig strictness options
NoInfer<T> — Prevent Unintended Inference
A smaller but practical addition from 5.4:
function createState<T>(initial: T, fallback: NoInfer<T>): T {
return initial ?? fallback
}
// Without NoInfer:
// createState('hello', 42) — T inferred as string | number (unwanted!)
// With NoInfer<T> on fallback:
createState('hello', 42)
// ❌ TypeScript error: 42 is not assignable to string
// T was inferred from `initial` only, not `fallback`
Practical Patterns: Combining New Features
The new TypeScript 5.x features compose well together. Here are patterns that combine multiple features:
Validated Configuration with satisfies + const type parameters
// Define a typed configuration builder
function defineConfig<const T extends Record<string, unknown>>(
config: T & Record<string, unknown>
): T {
return config
}
type AppConfig = {
database: { url: string; pool: number }
redis: { host: string; port: number }
features: Record<string, boolean>
}
// satisfies validates structure; const parameter preserves literal types
const config = defineConfig({
database: { url: 'postgres://...', pool: 10 },
redis: { host: 'localhost', port: 6379 },
features: { darkMode: true, betaSignup: false },
}) satisfies AppConfig
// config.features.darkMode is true (literal), not boolean
// config.database.pool is 10 (literal), not number
Resource Management with using + Inferred Type Predicates
// A database session factory with Symbol.asyncDispose
class DbSession {
private connection: Connection
constructor(connection: Connection) {
this.connection = connection
}
async query<T>(sql: string, params: unknown[]): Promise<T[]> {
return this.connection.query(sql, params)
}
async [Symbol.asyncDispose]() {
await this.connection.close()
}
}
async function getUsersWithRoles(db: DatabasePool) {
await using session = new DbSession(await db.acquire())
const users = await session.query<{ id: string; roleId: string | null }>(
'SELECT id, role_id as roleId FROM users',
[]
)
// Inferred type predicate — no manual annotation needed
const usersWithRoles = users.filter(u => u.roleId !== null)
// usersWithRoles: { id: string; roleId: string }[] ✅
return usersWithRoles
// ✅ session is closed automatically, even if query throws
}
Builder Pattern with const Type Parameters + satisfies
// A route builder that accumulates literal types
class TypedRouter<const Paths extends string = never> {
private handlers: Map<string, Handler> = new Map()
get<const P extends `/${string}`>(
path: P,
handler: (req: Request) => Response
): TypedRouter<Paths | P> {
this.handlers.set(path, handler)
return this as any
}
navigate(path: Paths) {
window.location.href = path
}
}
const router = new TypedRouter()
.get('/home', () => new Response('Home'))
.get('/dashboard', () => new Response('Dashboard'))
router.navigate('/home') // ✅
router.navigate('/dashboard') // ✅
router.navigate('/missing') // ❌ TypeScript error at compile time
Migration Notes
If you're upgrading from TypeScript 4.x:
satisfiesis additive — existing code works; addsatisfieswhere you want literal type preservationusingrequires a tsconfig target update — set"target": "ES2022"or higher, and"lib": ["ES2022"]or"ES2023"- Type predicate inference may change existing types — run
tsc --noEmitand review any newly flagged errors; some are genuine improvements revealing previously hidden bugs consttype parameters are purely additive — existing generics are unaffected; addconstwhere you want literal inference
Methodology
- TypeScript version history from the official TypeScript blog and GitHub releases
- Code examples compiled and verified against TypeScript 5.5.x
- npm download data from npmjs.com, March 2026
Track TypeScript's npm download trends and compare with alternatives like SWC and esbuild on PkgPulse.
Related: tsx vs ts-node vs Bun 2026 · Zod v4 vs ArkType vs TypeBox vs Valibot 2026 · TypeScript 5.x Features Every Developer Should Use