Understanding the Question Mark (?:) in TypeScript

While you are learning TypeScript and reading someone else’s code, you find there is the following declaration in a file of a project:

interface User {
   id: string;
   firstName: string;
   middleName?: string;
   lastName: string;
}

Why does the middleName property is followed by a question mark ?: ?

What does ?: mean in TypeScript?

Using a question mark followed by a colon (?:) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined.

Similar to using two types where one type is undefined

Another way to think about using a question mark with a colon (?:) is to define two types to a property where one of them is undefined. Hence, if we make a small modification to the User interface, the middleName property will have a type definition of string | undefined.

interface User {
   id: string;
   firstName: string;
   middleName: string | undefined;
   lastName: string;
}

Difference between ?: and using two types where one type is undefined

There is one problem with using middleName: string | undefined; instead of middleName?: string;. The middleName property is no longer optional.

Depending on the IDE you use, you can get immediate feedback saying the property is required. For example, try adding the following code, which creates an admin variable with a type of User that doesn’t have a middleName:

interface User {
  id: string;
  firstName: string;
  middleName: string | undefined;
  lastName: string;
}

const admin: User = {
  id: '92d4549c-ebe1-49b4-b113-26cf00d204bc',
  firstName: 'Andres',
  lastName: 'Reales'
};

You should notice the IDE displays the following error

Property 'middleName' is missing in type '{ id: string; firstName: string; lastName: string; }' but required in type 'User'.(2741)

right underneath the admin variable:

Errors when using string | undefined type definition

Unless the admin has a middleName value assigned, the code will not run due to compilation errors. In this case, using the question mark with a colon (?:) is a better alternative as it will make middleName property optional. In that way, we can update the type definition back to string only.

interface User {
  id: string;
  firstName: string;
  middleName?: string;
  lastName: string;
}

const admin: User = {
  id: '92d4549c-ebe1-49b4-b113-26cf00d204bc',
  firstName: 'Andres',
  lastName: 'Reales',
};

As you notice, the error disappears and the code will compile.

No errors when using question marks to define optional property

Using ?: with undefined as type definition

It is possible to use ?: with an undefined type. If you remember the example where we used string | undefined type definition for the middleName, we can add the ?: and keep the undefined type.

interface User {
   id: string;
   firstName: string;
   middleName?: string | undefined;
   lastName: string;
}

While there are no errors with this interface definition, it is inferred the property value could undefined without explicitly defining the property type as undefined. In case the middleName property doesn’t get a value, by default, its value will be undefined.

interface User {
  id: string;
  firstName: string;
  middleName?: string;
  lastName: string;
}

const admin: User = {
  id: '92d4549c-ebe1-49b4-b113-26cf00d204bc',
  firstName: 'Andres',
  lastName: 'Reales',
};

console.log(admin.middleName); // it will log: undefined

Question mark ?: is not only used with interfaces

In the initial example, we noticed the usage of the question mark with a colon (?:) when defining the properties of an interface. In a similar way, we can do so this when defining a class.

class Car {
  id: string;
  brand: string;
  model: string;
  year: number;
  price?: number;
}

This gives the flexibility to not having the need to provide values to all properties of a class.

We can also define optional parameters when creating functions in TypeScript.

function buyGift(itemId: string, accountId: string, message?: string) {
  const item = getItem(itemId);

  pay(item.price, accountId);

  if (message) {
    sendMessage(message);
  }
}

buyGift('item id', 'account id');

Notice how we didn’t need to pass all three arguments to trigger the buyGift function. Instead, we provided only the necessary arguments.

This is a big benefit in TypeScript when compared to defining functions in JavaScript as all of the parameters in JavaScript are optional, even if the logic inside the function requires them, while in TypeScript you have to explicitly add the question mark ?: to make parameters optional.

Using strictNullChecks TypeScript Compiler Option

If you don’t know, you can define TypeScript compiler options inside a tsconfig.json file. This file might have a structure like this:

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  }
}

Defining compiler options inside the compilerOptions property of the tsconfig.json file is a good way to enforce a set of rules in the code.

For instance, we know that by defining an interface with an optional property such as middleName?: string:

interface User {
   id: string;
   firstName: string;
   middleName?: string;
   lastName: string;
}

The possible values of middleName should be either undefined or a string value. However, what if I show you I can define the value of null to the middleName property like in the next code snippet?

const admin: User = {
  id: '92d4549c-ebe1-49b4-b113-26cf00d204bc',
  firstName: 'Andres',
  lastName: 'Reales',
  middleName: null
};

While this is not correct, TypeScript might or not display an error. If you are not getting an error, it is because you don’t have strictNullChecks compiler option. For the purposes of understanding this concept, go ahead and add strictNullChecks with a value of true in the TypeScript compiler options and save the changes made in your tsconfig.json file.

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true,
    "strictNullChecks": true
  }
}

If you go back to the code, you should see TypeScript displaying the following error:

Type 'null' is not assignable to type 'string | undefined'.(2322)
index.ts(4, 3): The expected type comes from property 'middleName' which is declared here on type 'User'
(property) User.middleName?: string | undefined

in the property middleName of the admin object.

Error when using strictNullChecks compiler option

In that way, we prevent potential logic issues from happening during runtime.

Conclusion

In this article, you learned what it means to use the question mark followed by a colon ?: in TypeScript as well as the difference between using ?: and using undefined type without the question mark. Also, you can enable strictNullChecks compiler option to prevent assigning null values to a property that is an option, unless null is one of the type definitions of the property.

Interested in Reading More About TypeScript?

I wrote other TypeScript articles, and I thought you might be interested in reading some of them since you are reading this.

Did you like this article?

Share your thoughts by replying on Twitter of Become A Better Programmer or to personal my Twitter account.