TypeScript·Intermediate
TypeScript Patterns I Use in Large Frontend Applications
3 Aug 2026 · 8 min read · Intermediate
- #TypeScript
- #Architecture
Share
TypeScript pays rent when types model real domain states, not when every value is a loose interface with optional everything.
Discriminated unions for UI state
Typescript
type LoadState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
function label(state: LoadState<string>): string {
switch (state.status) {
case 'idle':
return 'Waiting';
case 'loading':
return 'Loading…';
case 'success':
return state.data;
case 'error':
return state.message;
}
}Exhaustiveness checking catches missing cases when you add a new status.
Prefer closed string unions
Typescript
export const BLOG_CATEGORIES = [
'Angular',
'Vue',
'Next.js',
'React',
'TypeScript',
'Performance',
'Architecture',
] as const;
export type BlogCategory = (typeof BLOG_CATEGORIES)[number];Derive types from runtime arrays so filters and badges cannot drift.
Parse at the boundary
When reading markdown frontmatter or JSON APIs, validate once:
Typescript
function isDifficulty(
value: unknown,
): value is 'Beginner' | 'Intermediate' | 'Advanced' {
return (
value === 'Beginner' ||
value === 'Intermediate' ||
value === 'Advanced'
);
}Inside the app, trust the narrowed type. Do not sprinkle as casts through components.
Avoid any; prefer unknown then narrow
unknown forces handling. any silences the compiler and your teammates.
Branded ids when collisions hurt
Typescript
type ArticleSlug = string & { readonly __brand: 'ArticleSlug' };
function articleSlug(value: string): ArticleSlug {
return value as ArticleSlug;
}Use sparingly—for ids that must not mix with arbitrary strings.
Conclusion
Large frontend TypeScript is less about advanced generics and more about modeling states accurately and validating at the edges.