Optional vs. Undefined: Understanding TypeScript's Approach to Required Properties
Tags: typescript, webdev, javascript, tutorial
Published: 2023-03-19 08:30:00

> Also available on my [blog](https://nikolasbarwicki.com/articles/optional-vs-undefined-typescript-required-properties)

## Introduction

TypeScript is a powerful programming language that provides developers with the ability to specify static types for their code, catching errors at compile-time and improving code quality.

When working with TypeScript, developers often need to specify whether a property, type, or function parameter is optional or required, and whether it can have a value of `undefined`. This is typically done using either optional properties or union types with `undefined` as an option.

In this article, we will explore the differences between these two approaches and how they can be used to enforce required properties in interfaces, required parameters in functions, and required types in variable declarations. We will also provide practical examples to demonstrate the benefits and use cases of each approach.

## Is shorter code always better?

When it comes to defining optional properties in TypeScript, developers commonly use the shorthand notation with a question mark, such as `prop?: type`. However, it's important to note that this shorthand notation does not mean the same thing as using a union type with `undefined`, such as `prop: type | undefined`. The former denotes an optional property, while the latter denotes a required property that can be assigned the value of `undefined`. Therefore, it's crucial to use the correct notation depending on the desired behavior. Util functions for internal usage should also be carefully considered to determine if they should be callable without parameters at all.

```typescript
type OptionalProp = {
  prop?: string;
};

function funcOptional(propObj: OptionalProp): void {
  console.log(`Optional property value is ${propObj.prop}`);
}

funcOptional({ prop: undefined }); // Output: Optional property value is undefined
funcOptional({}); // Output: Optional property value is undefined
```

In this example, we define a type with an optional `prop` property, which can be `undefined`. The `funcOptional` function takes an object parameter of type `OptionalProp` and outputs the value of `prop`. When the function is called with an object that has `prop` set to undefined or is missing, the output will be `undefined`.

```typescript
type UnionProp = {
  prop: string | undefined;
};

function funcUnion(propObj: UnionProp): void {
  console.log(`Union type property value is ${propObj.prop}`);
}

funcUnion({ prop: undefined }); // Output: Union type property value is undefined
funcUnion({}); // Error: 'prop' is missing in type '{ }'
```

On the other hand, when we define a type with a union type that includes undefined as an option, the resulting type will require the property, but it can have a value of `undefined`. In the `funcUnion` function, when the `prop` parameter is called with an object that has prop set to `undefined`, the output will be undefined. However, if the function is called with an object that does not have the `prop` property, TypeScript will throw an error.

When choosing between these two methods, it's important to consider the use-case and whether it makes sense for the function or object to be invoked without the parameter or property at all.

## Example

Let's take a look at another code example to gain a better understanding of the difference.

```typescript
type Person = {
  name: string;
  age?: number;
};

function greet(person: Person): void {
  if (person.age) {
    console.log(`Hi ${person.name}, you're ${person.age} years old.`);
  } else {
    console.log(`Hi ${person.name}, I don't know how old you are.`);
  }
}

greet({ name: 'Alice', age: undefined }); // Output: Hi Alice, I don't know how old you are.
greet({ name: 'Bob' }); // Output: Hi Bob, I don't know how old you are.
```

In this example, the `Person` type has an optional `age` property, which can either be a number or `undefined`. The `greet` function takes a `Person` parameter and outputs a message that depends on whether the `age` property is present and has a value. When the function is called with a `Person` object that has an `age` property set to `undefined`, the output message indicates that the age is unknown.

```typescript
type PersonUnion = {
  name: string;
  age: number | undefined;
};

function greetUnion(person: PersonUnion): void {
  if (person.age !== undefined) {
    console.log(`Hi ${person.name}, you're ${person.age} years old.`);
  } else {
    console.log(`Hi ${person.name}, I don't know how old you are.`);
  }
}

greetUnion({ name: 'Alice', age: undefined }); // Output: Hi Alice, you're undefined years old.
greetUnion({ name: 'Bob' }); // Error: 'age' is missing in type '{ name: string; }'
```

In the second example, the `PersonUnion` type uses a union type to allow the `age` property to be either a `number` or `undefined`. The `greetUnion` function behaves similarly to `greet`, but in this case, the output message when the `age` property is `undefined` is different. When the function is called with a `PersonUnion` object that has an `age` property set to `undefined`, the output message indicates that the `age` is undefined. However, if the function is called with a `PersonUnion` object that does not have an `age` property, TypeScript will throw an error indicating that the `age` property is missing. This is because the `age` property is not optional in the `PersonUnion` type, and must be present in any object that conforms to this type.

## Summary

The main takeaway is that while shorter code may be desirable, it's crucial to use the correct notation depending on the desired behavior, and to carefully consider whether it makes sense for the function or object to be invoked without the parameter or property at all. Action points for the reader include carefully considering the use-case when choosing between optional properties or union types, and ensuring that util functions for internal usage are callable with parameters when necessary.