skip to content
Nikolas Barwicki - Javascript Blog Nikolas's Blog

Optional vs. undefined: Typescript's required properties

/ 5 min read

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.

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.

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.

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.

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.