TypeScript Best Practices in 2026: Advanced Patterns Every Full Stack Developer Should Know
Advanced TypeScript patterns and best practices for 2026 - covering type-safe APIs, branded types, discriminated unions, and modern project configuration.
Dibyank Padhy
Engineering Manager & Full Stack Developer
Table of Contents
TypeScript Has Won - Now Master It
TypeScript adoption has reached a tipping point where not using it is the exception, not the rule. The 2025 State of JS survey showed 89% of professional JavaScript developers use TypeScript regularly, and every major framework has first-class TypeScript support.
But most developers only scratch the surface. They add basic type annotations and call it a day. The real power of TypeScript lies in advanced patterns that catch bugs at compile time that would otherwise slip into production. Here are the patterns I use daily across my projects.
Pattern 1: Discriminated Unions for State Management
Stop using optional properties to model state. Use discriminated unions instead - they make impossible states unrepresentable:
// BAD: Optional properties allow invalid combinations
interface ApiResponse {
status: 'loading' | 'success' | 'error';
data?: User; // What if status is 'error' but data exists?
error?: string; // What if status is 'success' but error exists?
}
// GOOD: Discriminated union - each state is explicit
type ApiResponse =
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: string };
function renderUser(response: ApiResponse) {
switch (response.status) {
case 'loading':
return <Spinner />;
case 'success':
// TypeScript KNOWS data exists here
return <UserCard user={response.data} />;
case 'error':
// TypeScript KNOWS error exists here
return <ErrorMessage message={response.error} />;
}
}Pattern 2: Branded Types for Compile-Time Safety
Plain strings and numbers are the source of countless bugs. A userId and a postId are both strings, but passing one where the other is expected is a bug. Branded types catch this at compile time:
// Create unique branded types
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };
// Helper functions to create branded values
const createUserId = (id: string): UserId => id as UserId;
const createPostId = (id: string): PostId => id as PostId;
// Now the compiler catches misuse
function getPost(postId: PostId): Promise<Post> { ... }
function getUser(userId: UserId): Promise<User> { ... }
const userId = createUserId('usr_123');
const postId = createPostId('post_456');
getPost(userId); // COMPILE ERROR! Type 'UserId' not assignable to 'PostId'
getPost(postId); // OKPattern 3: Type-Safe API Routes with Zod
Validate API inputs at runtime while getting compile-time types for free:
import { z } from 'zod';
// Define schema once - get both validation AND types
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(['admin', 'user', 'viewer']),
age: z.number().int().min(13).max(150).optional(),
});
// Derive TypeScript type from schema - always in sync
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// Equivalent to:
// { email: string; name: string; role: 'admin' | 'user' | 'viewer'; age?: number }
// Use in your API handler
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
errors: result.error.flatten().fieldErrors
});
}
// result.data is fully typed as CreateUserInput
const user = await createUser(result.data);
return res.status(201).json(user);
});Pattern 4: The satisfies Operator
The satisfies operator, introduced in TypeScript 4.9 and now widely adopted, is perfect for configuration objects where you want type-checking without widening:
const routes = {
home: { path: '/', component: 'HomePage' },
about: { path: '/about', component: 'AboutPage' },
blog: { path: '/blog/:slug', component: 'BlogPage' },
} satisfies Record<string, { path: string; component: string }>;
// TypeScript still knows the exact keys
type RouteNames = keyof typeof routes; // 'home' | 'about' | 'blog'
// Without satisfies, using 'as const' would make everything readonly
// Without satisfies, using a type annotation would lose the specific keysPattern 5: Const Assertions and Template Literal Types
// Build type-safe event systems
type EventMap = {
'user:created': { userId: string; email: string };
'user:deleted': { userId: string };
'post:published': { postId: string; authorId: string };
};
class TypedEventEmitter {
private handlers = new Map<string, Set<Function>>();
on<K extends keyof EventMap>(
event: K,
handler: (payload: EventMap[K]) => void
): void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
}
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): void {
this.handlers.get(event)?.forEach(h => h(payload));
}
}
const emitter = new TypedEventEmitter();
// Fully type-safe - autocomplete for events AND payloads
emitter.on('user:created', (payload) => {
console.log(payload.email); // TypeScript knows this exists
});
emitter.emit('post:published', {
postId: '123',
// authorId is required - TypeScript enforces this
});Modern Project Configuration for 2026
Finally, here is the tsconfig.json I recommend for new projects in 2026:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}TypeScript is not just about adding types to JavaScript - it is a design tool that forces you to think about your data shapes and state transitions upfront. The patterns in this post represent the level of type safety that separates production-quality TypeScript from "JavaScript with some annotations."
Stay Updated
Get notified when I publish new articles on engineering, AI, and leadership. No spam, unsubscribe anytime.