DEV Community

Cover image for TypeScript has a Required Utility Type.
Mayowa Obisesan
Mayowa Obisesan

Posted on

TypeScript has a Required Utility Type.

TypeScript developers love safety nets. We want guarantees. We want to know that if something should exist, it must exist. So, as you might expect, with all things programming, there is an utility type in TypeScript for that.

Required in TypeScript is a built-in utility type that quietly enforces presence where you don’t want optional properties. It helps to ensure that fields or type that are marked as required have to be defined.

Let’s dissect what Required does, how it works, and why it deserves a permanent place in your TypeScript codebase.

What Is the Required Utility Type?

TypeScript’s Required is a mapped type that transforms all optional properties of a given type T into required ones. It’s part of TypeScript’s standard library and is defined like this

type Required<T> = {
  [P in keyof T]-?: T[P];
};
Enter fullscreen mode Exit fullscreen mode

In plain English, what this means is that you loop through each property P in type T, and remove the optional modifier ?. The -? syntax specifically removes optionality. The result is a new type where all properties are now mandatory.

A Quick Example

Here’s a practical use case:

In this first example, name and email are optional,

type User = {
  id: number;
  name?: string;
  email?: string;
};
Enter fullscreen mode Exit fullscreen mode
type UserIsRequired = Required<User>;
Enter fullscreen mode Exit fullscreen mode

But now that User is marked as Required above, UserIsRequired now has all properties required.

type UserIsRequired = {
  id: number;
  name: string;
  email: string;
};
Enter fullscreen mode Exit fullscreen mode

No more optional name or email. If you’re consuming an object typed as UserIsRequired, TypeScript will enforce that those fields are present.

Why Is This Useful?

Think of it this way, optional fields are a double-edged sword. They’re helpful when you’re dealing with partial data, but they can turn into bugs if you assume those fields are always present later in the pipeline, and this can later come back to bite you.

Consider a user registration form:

function submitForm(user: User) {
  sendToServer(user.name.toUpperCase()); // name is undefined here
}
Enter fullscreen mode Exit fullscreen mode

You either use non-null assertions (user.name!) and hope for the best, or check every field. But what if you know this function should only ever receive complete user data? That’s where Required<User> saves you.

function submitForm(user: Required<User>) {
  sendToServer(user.name.toUpperCase()); // name is now a guaranteed string
}
Enter fullscreen mode Exit fullscreen mode

This makes your codebase safer and your intent clearer.

So, Where Should You Use It?

1. Validation Layers

After validating incoming data, you might want to treat it as fully defined. Here is an example of what I mean by this.

function validate(user: User): Required<User> {
  if (!user.name || !user.email) throw new Error("Missing fields");
  return user as Required<User>;
}
Enter fullscreen mode Exit fullscreen mode

2. Default Merging Utilities

When combining defaults with user input. Here’s an example of this.

function withDefaults(user: User): Required<User> {
  return {
    id: user.id,
    name: user.name ?? 'Anonymous',
    email: user.email ?? 'no-reply@example.com',
  };
}
Enter fullscreen mode Exit fullscreen mode

Now the return type explicitly communicates completeness.

3. Testing & Mock Data

When creating test data before integrating with served data from a server or an external API.

const mockUser: Required<User> = {
  id: 1,
  name: "Alice",
  email: "alice@example.com"
};
Enter fullscreen mode Exit fullscreen mode

No half-baked objects will slip through this way.

Limitations of Required

  1. It’s Shallow: Required only makes top-level properties required. Nested objects with optional fields won’t be affected unless you apply Required recursively.
type Config = {
  meta?: {
    created?: string;
  };
};
Enter fullscreen mode Exit fullscreen mode
type FullyRequired = Required<Config>;

// meta is required, but meta.created is still optional
Enter fullscreen mode Exit fullscreen mode

2. Type Assertions Are on You: If you claim something is Required, make sure it truly is. TypeScript trusts you once you assert it. This can also come back to bite you. So, it’s good to have a sense of this constraint when using Required.

Final Thoughts

TypeScript’s Required utility type is a small but powerful feature that enforces intent and safeguards your logic. It’s your ally in a world full of partials and maybes. You should use it to tighten up your types, document your expectations, and write safer, cleaner code.

In short, Required<T> isn’t just syntactic sugar in your code, it is a contract. And in software, clear contracts are pure gold.

Did you Learn Something?

If you like this article, give it a reaction.

If you have a question or an opinion or controversy, drop a comment.

If you want to read about other programming tips. Check out my other articles. You will definitely learn something new from there.

Thanks for reading. 🙂

Top comments (0)