I recently found myself writing the same kind of TypeScript type transformation in a few different places.
Hello,
I'm Vikash Kumar.
I had an existing type where most properties were required, but for one particular use case, I wanted some of them to become optional.
At first, I was just writing the transformation inline.
Then I realized:
Why not turn the pattern into a reusable utility?
So I built this:
export type Optional<T, K extends keyof T> =
Omit<T, K> & Partial<Pick<T, K>>;
And honestly, this tiny utility has been surprisingly useful.
The problem
Imagine we have a configuration type:
type Config = {
name: string;
version: string;
description: string;
};
Normally, TypeScript expects all three properties:
const config: Config = {
name: "my-app",
version: "1.0.0",
description: "A demo app",
};
But what if description is optional in a particular function?
We could create another type manually:
type PartialConfig = {
name: string;
version: string;
description?: string;
};
This works.
But now we have duplicated our type definition.
And duplication gets annoying when the original type becomes bigger.
Enter Optional<T, K>
With the utility:
type PartialConfig = Optional<Config, "description">;
Now TypeScript understands:
const config: PartialConfig = {
name: "my-app",
version: "1.0.0",
};
name and version remain required.
description becomes optional.
That's it.
No duplicate type.
No manual rewriting.
What's actually happening here?
The utility looks complicated at first:
Omit<T, K> & Partial<Pick<T, K>>
But it's really just combining three TypeScript utilities.
Pick
Pick<T, K>
Selects the properties we care about.
For example:
Pick<Config, "description">
becomes:
{
description: string;
}
Partial
Partial<Pick<T, K>>
makes those selected properties optional:
{
description?: string;
}
Omit
Omit<T, K>
keeps everything except those properties:
{
name: string;
version: string;
}
Finally, we combine them:
Omit<T, K> & Partial<Pick<T, K>>
Which gives us:
{
name: string;
version: string;
description?: string;
}
Why this is useful?
This pattern becomes especially handy when working with APIs, configuration objects, constructors, and update functions.
For example, imagine an update function:
type User = {
id: string;
name: string;
email: string;
role: "admin" | "user";
};
When updating a user, we probably don't want to require every property.
But perhaps id should still be required:
type UserUpdate = Optional<
Pick<User, "id" | "name" | "email" | "role">,
"name" | "email" | "role"
>;
Now:
const update: UserUpdate = {
id: "123",
};
is perfectly valid.
And so is:
const update: UserUpdate = {
id: "123",
role: "admin",
};
But this won't compile:
const update: UserUpdate = {
role: "admin",
};
Because id is still required.
That is where these small generic utilities start becoming really powerful.
The interesting part
What I like most about this isn't the utility itself.
It's realizing that TypeScript lets us build our own vocabulary for common type patterns.
Instead of repeatedly expressing:
Omit<T, K> & Partial<Pick<T, K>>
I can simply say:
Optional<T, K>
The type system becomes easier to read.
And once a pattern appears often enough in your codebase, turning it into a generic utility can make the whole project feel cleaner.
I used to think TypeScript utility types were mostly something I needed to memorize.
Lately, I've started thinking about them differently:
They're building blocks.
Once you understand Pick, Omit, Partial, intersections, and generics, you can start composing your own utilities for the problems your codebase actually has.
And that's probably the fun part. 😄
Have you built a small TypeScript utility recently that ended up becoming surprisingly useful?
Top comments (0)