Built-in types beat custom aliases

TypeScript ships with utility types that solve 80% of shape-transform problems. Reaching for interface Foo extends Bar every time often creates duplication you don't need.

Utility types are defined in TypeScript's standard library — no imports required.

Pick and Omit

When you only need a subset of fields:

code
interface Post {
  title: string
  slug: string
  date: string
  content: string
  featured: boolean
}
 
type PostMeta = Pick<Post, 'title' | 'slug' | 'date'>
type PostInput = Omit<Post, 'slug' | 'date'>

Pick keeps listed keys. Omit removes them — useful for create forms where the server generates slug and date.

Partial and Required

Update endpoints often accept optional fields:

code
type PostUpdate = Partial<Pick<Post, 'title' | 'description' | 'featured'>>
 
function updatePost(slug: string, data: PostUpdate) {
  // data.title might be undefined — that's intentional
}

Flip it with Required<T> when every key must be present after validation.

Record for dictionaries

Maps and lookup tables read cleaner with Record:

code
type TagColor = 'react' | 'typescript' | 'css' | 'default'
 
const tagStyles: Record<TagColor, string> = {
  react: 'tag-pill--react',
  typescript: 'tag-pill--typescript',
  css: 'tag-pill--css',
  default: 'tag-pill--default'
}

ReturnType and Parameters

Extract types from functions instead of duplicating them:

code
async function fetchPosts() {
  return [{ title: 'Hello', slug: 'hello' }] as const
}
 
type Posts = Awaited<ReturnType<typeof fetchPosts>>
type FetchPostsArgs = Parameters<typeof fetchPosts>

This keeps API wrappers and mocks in sync when the function signature changes.

Duplicated shape
interface PostsResponse {\n  posts: { title: string; slug: string }[]\n}\n\nasync function getPosts(): Promise<PostsResponse>
Inferred shape
async function getPosts() {\n  return { posts: await db.post.findMany() }\n}\n\ntype PostsResponse = Awaited<ReturnType<typeof getPosts>>

Exclude and Extract

Narrow unions when handling status codes or theme modes:

code
type Status = 'draft' | 'published' | 'archived'
type LiveStatus = Exclude<Status, 'draft'>
 
type AlertType = 'info' | 'warning' | 'error' | 'tip'
type DangerAlert = Extract<AlertType, 'warning' | 'error'>

NonNullable

Filter null from inferred types:

code
function getFeaturedPost(posts: PostMeta[]) {
  return posts.find((p) => p.featured) ?? null
}
 
type Featured = NonNullable<ReturnType<typeof getFeaturedPost>>

Conclusion

Utility types are composable — Partial<Pick<Post, 'title'>> is valid and often exactly what an PATCH handler needs. Start with Pick, Omit, and Partial; reach for ReturnType when bridging functions and components.

If a utility type gets hard to read, give it a named alias. Readability still wins over clever one-liners.