Hey there, fellow coder!
Ever found yourself writing the same TypeScript code over and over and thinking, There has to be a better way? Well, good news—there is! TypeScript comes with a set of built-in Utility Types that can save you time, make your code cleaner, and (let’s be honest) make you feel like a TypeScript wizard.
If you’re already using TypeScript, you know it’s all about type safety and catching errors before they become headaches. But here’s the thing: manually defining types for every possible scenario? That’s a recipe for unnecessary work. Instead, TypeScript gives us these built-in shortcuts—Utility Types—that help us shape, refine, and transform our types effortlessly.
I’m Alvison Hunter, a software enthusiast from Nicaragua, and today, we’re going to break down these Utility Types with real-world examples so you can start using them right away. No fluff, just practical knowledge you can put to work.
Let’s get started!
What the Heck is a TypeScript Utility Type?
Alright, let’s be real—TypeScript can feel like magic sometimes But if you've ever looked at its built-in utility types and thought, Wait... what are these bloody things?, you're not alone, buddy!
So, what is a TypeScript Utility Type, really? In simple terms, it's a built-in generic type that helps you tweak and shape other types without breaking a sweat. Think of them as shortcuts for handling common type transformations. Instead of reinventing the wheel every time you need to modify a type, TypeScript gives you these handy tools to make your life easier.
Why should you care? Well, these utility types do a few really important things:
- Make your code cleaner—less repetition, more clarity.
- Boost type safety—TypeScript does more of the heavy lifting to catch errors before they happen.
- Save you time—because who wants to manually define types when you can just reuse these powerful helpers?
If you're working with complex types, utility types are basically your best friend. They help you express intent more clearly and keep your codebase maintainable. And once you get the hang of them, you'll wonder how you ever coded without them.
So, if you've been ignoring utility types, it's time to start paying attention. They’re here to make TypeScript work for you, not against you.
Utility Types Code Examples:
Alright, let’s talk about Partial. This little utility type in TypeScript is a game-changer when you don’t want to deal with every single property in a type. It basically takes an existing type and makes all its properties optional. Super handy, right?
Let’s say you’ve got a Developer interface, but now you need to create a BackendDeveloper without defining every single key upfront. Instead of manually making everything optional (which, let’s be honest, would be a pain), Partial does the heavy lifting for you.
Here’s how it works in action:
interface Developer {
name: string;
age: number;
skills: string[];
experience: number;
}
type BackendDeveloper = Partial<Developer>;
Boom! Now BackendDeveloper has all the same properties as Developer, but they’re optional. That means you can do this:
interface Developer {
name: string;
age: number;
skills: string[];
experience: number;
}
type BackendDeveloper = Partial<Developer>;
const RequestedDeveloper: BackendDeveloper = {
name: "Alvison Hunter",
age:49,
skills: ["Node.js", "TypeScript","JavaScript","Python"]
};
console.log(RequestedDeveloper);
No need to specify name, age, or experience if you don’t want to. TypeScript won’t complain. In this case, I ignored 'experience'.
This is perfect when you're working with things like form data, partial updates, or flexible API responses where you don’t always have all the information at once. Instead of cluttering your types with endless ? marks, just wrap it in Partial, and you’re good to go.
Next time you find yourself needing a type with optional properties, remember Partial. It keeps your code cleaner and your sanity intact.
Alright, let’s talk about Required<>. This little TypeScript utility ensures that specific properties in a type aren’t just suggestions—they’re absolutely mandatory. No more "optional" nonsense when you really need something to always be there.
Imagine we’re working with a SoftwareEngineer type. By default, some properties might be optional, like the name. But let’s say we’re dealing with Alvison Hunter—an experienced developer (not that he's bragging or anything). And for whatever reason, we want to make sure all of his information is always present.
Here’s how that looks in TypeScript:
interface SoftwareEngineer {
name?: string; // Optional by default
stack: string;
languages: string[];
}
// Enforce that all properties must be provided
const NewSoftwareEng: Required<SoftwareEngineer> = {
name: 'Alvison Hunter',
stack: 'MERN',
languages: ['JavaScript',
'TypeScript',
'Python',
'Golang',
'C#'],
};
console.log(NewSoftwareEng);
See what happened there? With Required<>, all the properties that were once optional must now be included. No missing names, no gaps in data.
This is super useful when you’re working with objects that start out flexible but later need to be fully fleshed out. Maybe you allow partial data when users first sign up but require everything before they can proceed. Required<> makes sure you don’t accidentally forget something important.
And that’s pretty much it! Simple, but super handy.
So, what if we want a more streamlined version of a PythonDeveloper interface—one with fewer properties? Maybe we’re overusing a particular field (looking at you, name), or we just don’t need it in a certain context. That’s where Omit comes in.
The Omit utility type lets us create a new type by excluding specific properties from an existing one. Think of it like making a custom version of an interface, but without the clutter of fields we don’t need.
Let’s say we want a PythonDeveloper type but without country. Maybe we already have that info elsewhere, and we just want to focus on other details. Here’s how we’d do it:
interface PythonDeveloper {
name?: string;
country: string;
language: string;
}
type PythonDevWithoutCountry = Omit<PythonDeveloper, 'country'>;
const newPythonDev: PythonDevWithoutCountry = {
name: 'Declan Hunter',
language: 'Python',
};
console.log(newPythonDev);
Boom! PythonDevWithoutCountry now has everything from PythonDeveloper—except country. That means if you try to add country to newPythonDev, TypeScript will call you out on it.
This is super handy when you want to tweak types without completely redefining them. It keeps things flexible and avoids unnecessary duplication. Less code, fewer headaches.
Pretty neat, right?
Alright, we’ve laid down some solid groundwork, but let’s get a little more precise. When working with data, we don’t always need the whole thing—sometimes, we just want the juicy bits. So, is there a way to be more selective about the information we extract from a type in TypeScript? Can we handpick only the attributes that actually matter to us?
Yep, and that’s exactly where Pick comes in. Think of it as a filter for TypeScript types—it lets us grab only the properties we care about while ignoring the rest. Pretty handy, right?
Let’s say we’re working with a JavaDeveloper type, but for some reason, we don’t need all its properties. Maybe we only care about a developer’s name and country, and we also want to add a new property, strongLanguage. Here’s how we’d do it using Pick:
interface JavaDeveloper {
name?: string;
country: string;
language: string;
}
type CoreAttributes = Pick<JavaDeveloper, "name" | "country"> & {
strongLanguage: string;
};
const newJavaDeveloper: CoreAttributes = {
name: "Alexander Ruiz",
country: "Nicaragua",
strongLanguage: "Java",
};
console.log(newJavaDeveloper);
See what happened there? We used Pick to grab only name and country from JavaDeveloper, and then we added strongLanguage separately. This keeps our type focused on just what we need—nothing more, nothing less.
And just like that, we’ve got a cleaner, more manageable structure. No unnecessary properties floating around, no clutter—just the essential data, neatly packaged.
So next time you’re dealing with a complex type but only need a few pieces of it, remember: Pick is your friend.
Alright, we’re doing great so far—these tools are seriously pulling their weight and making our lives a whole lot easier. But let’s kick it up a notch. What if we could make sure that once we set these properties, they stay locked in, untouchable?
That’s where Readonly comes in. If you ever find yourself in a situation where immutability is a must, the Readonly utility type is your best friend. It tells TypeScript, “Hey, once these values are set, hands off—no modifications allowed.”
Let’s see it in action:
interface PythonDeveloper {
name?: string;
country: string;
language: string;
}
const newPythonDev: Readonly<PythonDeveloper> = {
name: 'Alvison Hunter',
country: 'Nicaragua',
language: 'Python',
};
// Error: Cannot assign to 'name' because it is a read-only property.
newPythonDev.name = 'Alvison Lucas Hunter Arnuero';
console.log(newPythonDev);
See that error? That’s TypeScript stepping in to protect us from accidentally changing something we shouldn’t. Pretty handy, right?
So if you’ve got data that should never be modified after it’s set, Readonly is a simple yet powerful way to enforce that rule. Give it a try—you’ll thank yourself later.
Alright, let's take a second and really think about it—what about the wild and wonderful world of object shapes in TypeScript? If that thought just made your brain do a little backflip, don’t worry, I’ve got you. And to answer your unspoken question: Yes, TypeScript has a fantastic way to handle this!
Let’s talk about Record. This handy utility type lets you create an object type where the keys come from a predefined set of string literals. Think of it as a blueprint for structuring objects dynamically, but in a way that keeps TypeScript happy (which, as you know, is half the battle).
For example, let’s say we’re building a team of developers, and we want to assign them unique identifiers—because, well, we’re organized like that. Here’s how Record makes it ridiculously simple:
interface Developer {
name?: string;
country: string;
language: string;
}
type DevTeam = Record<'javascriptDev' | 'pythonDev' | 'javaDev', Developer>;
const team: DevTeam = {
javascriptDev: {
name: 'Alvison Hunter',
country: 'Nicaragua',
language: 'JavaScript'
},
pythonDev: {
name: 'Declan Hunter',
country: 'Unknown', language: 'Python'
},
javaDev: {
name: 'Alexander Ruiz',
country: 'Unknown',
language: 'Java'
},
};
console.log(team);
See what’s happening here? Instead of manually defining each key, Record takes care of that for us by ensuring that every key in our DevTeam object matches one of the given string literals. And since TypeScript knows exactly what to expect, we get all the type safety goodness without extra work.
So, next time you need to define an object with strict, predictable keys, let Record do the heavy lifting. Trust me, your future self will thank you.
AAlright, let’s talk about one of TypeScript’s unsung heroes: the ReturnType utility type. If you’ve ever been curious about how to easily extract the return type of a function (like whether it’s a string, boolean, number, or even an object), then this one’s for you!
So here’s the deal. The ReturnType utility type gives you exactly that: the return type of any given function. It’s super handy, especially when you're working with complex functions and need to extract or ensure that the return type is exactly what you expect. It's like having a built-in type checker, but without all the extra effort.
Let’s walk through an example. Imagine a scenario where we have a function that greets a WordPress developer with a personalized message. Alvison Hunter, being the awesome developer that he is, wrote this function:
interface WordPressDeveloper {
name: string;
pluginDev: boolean;
blockEditor: string;
}
type greetWordPressDevProps = Pick<WordPressDeveloper, "name">;
const generateWelcomeMessage = ({ name }: greetWordPressDevProps): string => {
return `Welcome, ${name}!`;
};
const TwelcomeMessage: ReturnType<typeof generateWelcomeMessage> = "Alvison";
console.log(typeof TwelcomeMessage);
Now, in this example, we’re using ReturnType to grab the return type of generateWelcomeMessage. This function returns a string (the welcome message), so the TwelcomeMessage variable will have the type string. The cool part is that ReturnType does all the heavy lifting for you. You don’t need to manually specify the type of the return value; TypeScript figures it out automatically.
To be honest, ReturnType is like your personal assistant that helps you stay organized and precise with your types. And as you can see, it makes your code a lot cleaner and easier to maintain—especially in larger projects where functions are returning more complicated objects or values.
So next time you need to know what a function is returning, just reach for ReturnType. It’s a small trick, but it can save you a lot of headaches down the road!
Alright, here’s the deal—you're feeling pretty good about how things are going, but now you've got a burning question: can we pull types from function parameters? The short answer is, yep, we totally can! In fact, we saw this coming, and it’s exactly why I’m here to introduce you to the Parameters utility type in TypeScript.
So, let’s break this down. The Parameters utility type does something super handy: it grabs the types of the parameters that a function takes. This can be a game-changer when you're trying to reuse or work with functions in a dynamic way, especially when you don’t want to manually retype each parameter.
Here’s a simple example. Let’s say you’ve got this function, greetDeveloper, that takes two parameters: name (a string) and country (another string). It looks something like this:
const greetDeveloper = (name: string, country: string): string => {
return `Welcome, ${name} from ${country}!`;
}
Now, instead of manually defining the types for the parameters again somewhere else, you can simply use the Parameters utility type to extract them like this:
type ParametersType = Parameters<typeof greetDeveloper>;
This line grabs the parameter types from the greetDeveloper function, so ParametersType will now be equivalent to [string, string]—super convenient, right?
But it gets better! Let’s say you want to create a new function that takes those parameters and does something with them. You could write something like this:
const fnWithRetrievedParams = (params: ParametersType) => {
const [name, country] = params;
return [name, country];
}
And, voila! You’ve now got a function that works with the parameters from greetDeveloper without manually specifying the types. All that’s left is to call it with the correct values:
console.log(fnWithRetrievedParams(["Alvison", "Nicaragua"]));
Pretty sweet, right? You just saved yourself from a lot of repetitive type declarations, and the best part is, this method keeps everything nice and flexible. It’s a small but powerful way to streamline your TypeScript code—just make sure you understand how it works so you can use it to its fullest potential.
Trust me, once you start using Parameters, you’ll wonder how you ever lived without it!
Alright, let’s talk about handling null values in TypeScript and how to avoid them when dealing with parameters. You know, sometimes we work with data that could be null or undefined, and we don’t want those values sneaking into our code because they could cause errors or unexpected behavior.
So, what can we do about it? Well, here’s where TypeScript’s NonNullable utility type comes to the rescue. It helps us make sure that we’re working with values that are neither null nor undefined. No more worrying about accidentally passing a null where you need an actual value!
Imagine this scenario: we have an object that represents a developer, and this developer's title could be null. Let’s say we don’t want that to happen, and we want to ensure that the title always has a valid string value. Here's how we can do it:
type DeveloperWithNullableTitle = {
name: string;
title: string | null;
country: string;
};
type NoNullsKeysProps<T> = {
[K in keyof T]: NonNullable<T[K]>;
};
const nonNullableWebDevObj: NoNullsKeysProps<DeveloperWithNullableTitle> = {
name: 'Alvison Hunter',
title: "Senior Web Developer",
country: 'Nicaragua',
};
console.log(nonNullableWebDevObj);
In this code, we have a DeveloperWithNullableTitle type, where the title property is allowed to be string or null. But then, we introduce a new type, NoNullsKeysProps, that applies the NonNullable utility type to every property of the object. So, now, even if we try to assign null to any of these properties, TypeScript will catch it, and we’ll be forced to pass a valid value.
It's a neat way to enforce stricter type safety in your code, especially when you're dealing with data that could be incomplete or missing. So next time you're unsure if something might be null, just throw on the NonNullable and you're good to go!
Alright, buckle up, because we're about to take this thing up a notch! So, we've been talking about generics, but now let’s talk about something a little more mind-blowing — types within promises.
Trust me, if you're not already using this, you're going to love it. We're going to focus on something called Awaited. It’s like a secret weapon for handling types in async functions. In simple terms, when you’re working with promises, you usually care about the value they resolve to, right? That’s where Awaited comes in.
Imagine this: You have a promise that resolves to some data. But how do you know what type that data is going to be? Without some extra work, it’s like you're guessing in the dark. Here’s where Awaited comes to the rescue. It’s a utility type that pulls out the actual type of the resolved value of a promise. Pretty cool, right?
Let’s take a closer look with some code. We’ll create a function that simulates fetching data from an API (don't worry, it's just a fake one):
type TPromiseReturnType = {
name: string;
title: string;
country: string;
};
const fakeResponse = {
name: "Alvison Hunter",
title: "Senior Web Developer",
country: "Nicaragua",
};
async function fetchData(): Promise<TPromiseReturnType> {
return fakeResponse;
}
type TfetchedDataReturnType = Awaited<ReturnType<typeof fetchData>>;
// Use an async function to await the fetchData promise
async function getFetchedData() {
let fetchedData: TfetchedDataReturnType = await fetchData();
console.log(fetchedData);
}
getFetchedData();
Now, here’s what’s going on. We have this function fetchData that returns a promise of TPromiseReturnType — that's the type we expect to get when the promise resolves. But, we don’t want to manually type-check this every time. Instead, we use Awaited, which automatically grabs the type of the resolved value from that promise. So, TfetchedDataReturnType is really just the type of whatever fetchData resolves to.
And then, inside getFetchedData(), we use await to grab that data. When the promise resolves, TypeScript already knows exactly what type it’s dealing with, thanks to Awaited. No more guesswork. No more manual type extraction. It just works.
So, to be honest, if you’re working with promises and you want to keep your types tight and clear, this little trick is a game-changer. And, as you might know, having that type safety when dealing with async code can save you a lot of headaches down the road. So, next time you’re handling promises, think about how you can level up your types with Awaited!
Alright, let’s close this deal on utility types in TypeScript. We've learned that they’re not just any ordinary tools — they’re like the unsung heroes of your code. Seriously, they help you write cleaner, more efficient code with way less effort. If you’ve been coding for a while, you know how important it is to keep things simple and readable. That’s where utility types come in, making sure your code stays sharp and easy to follow.
Now, I know it might feel like we've just scratched the surface here, but honestly, this is just the beginning. TypeScript’s utility types are a treasure trove of options, and there’s so much more to discover. So, don’t stop here — keep tinkering with them, testing them out in your own projects. The more you play around with them, the easier it’ll get to use these tools like a pro.
And hey, if you’ve enjoyed reading through this, I hope you feel inspired to keep pushing your code even further. There’s no limit to what you can do when you get the hang of these utility types.
Thanks for joining me on this little coding adventure — I had a blast sharing this with you. Let’s keep building cool stuff, breaking things (in a good way), and exploring all the ways we can make our code more awesome. Catch you in the next one, and remember: the code doesn’t write itself — but with utility types, it sure feels like it does!
❤️ Enjoyed the article? Your feedback fuels more content.
💬 Share your thoughts in a comment.
🔖 No time to read now? Well, Bookmark for later.
🔗 If it helped, pass it on, dude!
Top comments (3)
Good article
Thanks Pal!
Take your TypeScript skills to new heights with "Mastering TypeScript Core Utility Types":
📖 Buy on Leanpub
📖 Buy on Amazon