TypeScript Advanced Patterns 2026: The Complete Problem-Solving Guide
By Huzi
File #532 · 4 min read
Here is how the disaster starts: an API returns four different shapes on a status field, nobody models it, and a teammate writes user.data.profile.email on a payload with no data key. TypeScript was supposed to prevent this, but a loose union of overlapping optional interfaces defeats it as thoroughly as no types at all.
The quick answer: The fix is not more types — it is better patterns. Model the four shapes as a discriminated union on status, derive consumers with infer, brand validated primitives, keep switches exhaustive with never. The whole game is moving the 2 AM bug to 2 PM, with the compiler pointing at it.
Derive, do not duplicate
Conditional types are if-statements for the type system; infer declares a type variable the compiler fills by matching structure — the trick behind zod, tRPC and TanStack Query. Point it at your fetch functions and the consumer type never drifts from the producer:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type ElementOf<T> = T extends (infer U)[] ? U : never;
type AwaitedReturn<T extends (...args: any[]) => any> =
T extends (...args: any[]) => Promise<infer R> ? R : ReturnType<T>;
// Derived straight from the producer:
type User = AwaitedReturn<typeof fetchUser>;
Shape object types in one pass
Mapped types iterate over an object's keys and transform them together — the mechanism behind Partial and Pick. Two utilities earn their place everywhere: DeepReadonly freezes nested objects so cached API responses cannot be mutated by accident, and Optional makes only chosen keys optional — exactly a PATCH body:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
Brand the primitives
TypeScript is structurally typed: a UserId and an Email are interchangeable as far as the compiler cares — until you brand them. A brand is a phantom property that exists only at the type level, costs nothing at runtime, and refuses any raw string where a validated one belongs:
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, 'UserId'>;
const toUserId = (s: string): UserId => {
if (!/^[a-z0-9]{8,32}$/.test(s)) throw new Error('Invalid user id');
return s as UserId;
};
getUser('abc12345'); // Error: string is not assignable to UserId
getUser(toUserId('abc12345')); // OK — validated, then branded
Brand the strings that reach your query builder and SQL injection becomes a compile error instead of a CVE.
A validated document and a hopeful one are different types, too. HTG Travels brands every passport scan and hotel voucher before it enters a file.
Make "this or that" exhaustive
Discriminated unions give every variant a shared literal field, so a switch narrows each case: the right way to model API responses, async results and state machines. A never check in the default branch flags every stale switch the moment you add a variant:
type Result<T, E = Error> =
| { status: 'ok'; data: T }
| { status: 'error'; error: E }
| { status: 'loading' };
function handle<T>(r: Result<T>): string {
switch (r.status) {
case 'ok': return `Done: ${JSON.stringify(r.data)}`;
case 'error': return `Failed: ${r.error.message}`;
case 'loading': return 'Loading…';
default: {
const unhandled: never = r; // add a status, every stale switch breaks
return unhandled;
}
}
}
| The pattern | What it is for |
|---|---|
Conditional types + infer |
Derive consumers from producers — never drifts |
Mapped types (DeepReadonly, Optional) |
Freeze cached responses; build PATCH bodies |
| Branded primitives | Refuse raw strings where validated ones belong |
Discriminated unions + never |
Exhaustive switches that break loudly on new variants |
Pro tip: Prefer
satisfiesover a plain annotation — validation without widening your literals — and reach forunknowninstead ofany, which forces narrowing before use.
It makes me think of developers in Gaza who reopen their editors after every power cut and let the type checker tell them what survived — a small, honest mercy in an unreliable world.
We keep promises typed at HTG Travels, the licensed Sialkot desk. Every Umrah package carries the hotel names and their distance from the Haram in writing, and the tier sheet is yours to compare with anyone's; we are comfortable being checked. WhatsApp +92 317 5054959 for a remote month somewhere greener.




