Editor’s Note: This blog was reviewed for accuracy on 4 April 2023. Since publication, Microsoft released TypeScript v5, which includes the addition of decorators, support for multiple config files in extends
, a --module_resolution
bundler, and more. For more information, check out the TypeScript docs and GitHub repo.
The exclamation mark !
is known as the non-null assertion operator in TypeScript. We will be using these terms interchangeably in this article. But what does this operator do?
In this article, we will take a look at:
The non-null assertion operator tells the TypeScript compiler that a value typed as optional cannot be null
or undefined
. For example, if we define a variable as possibly a string or undefined, the !
operator tells the compiler to ignore the possibility of it being undefined.
Let’s say a variable is defined as possibly null or undefined, like so:
let x: string | undefined
Or, let’s say a function is defined to accept an optional argument, like so:
function printString (str ?: string) { … }
In these cases, if we try to reference that variable as a definite type, then the TypeScript compiler would give us an error message, such as the following:
Object is possibly 'undefined'. ts(2532)
We can use the non-null assertion operator to tell the compiler explicitly that this variable has a value and is not null
or undefined
. Let’s review a few examples to better understand the exclamation mark in TypeScript.
string | null
for a function that accepts string
Let’s say we defined a variable word
with the type as string | null
. This means throughout our code, word
can either hold a string
value or a null
value.
If we attempt to use a function only available to string
types on word
, TypeScript will reject it because there is a possibility in our code that word
holds a null
value type:
let word : string | null = null const num = 1 if (num) { word = "Hello World!" } console.log(word.toLowerCase()) // Error: Object is possibly 'null'.ts(2531)
Using the !
non-null assertion operator, we can tell TypeScript we are certain word
will never be null
(or undefined
), so it can confidently apply string functions to it:
let word : string | null = null const num = 1 if (num) { word = "Hello World!" } console.log(word!.toLowerCase())
With this small addition, the compiler no longer believes there is a possibility that word
is null.
In another example, let’s say we created a function printName
that accepts an optional argument personName
.
Note that defining a function argument as optional using ?:
is the same as defining type as possibly undefined. For example, arg?: string
is the same as arg: string | undefined
.
If we try to reassign that optional argument personName
to another variable of type string
, the following would occur:
function printName(personName?: string) { const fullName: string = personName /** * Error: Type 'string | undefined' is not assignable to type 'string'. * Type 'undefined' is not assignable to type 'string'. */ console.log(`The name is ${fullName}`) }
We can fix the TypeScript errors thrown in our snippet above using the !
operator:
function printName(personName?: string) { const fullName: string = personName! console.log(`The name is ${fullName}`) }
Now, the compiler understands that personName cannot be null or undefined, making it assignable to type string
.
In our final example, we will define a type Person
and a function printName
that accepts an optional argument of type Person
. Let’s see what happens if we try to use printName
to print the name attribute of Person
:
interface Person { name: string age: number } function printName(person?: Person) { console.log(`The name is ${person.name}`) // Error: Object is possibly 'undefined'. ts(2532) }
Let’s fix this TypeScript error using our !
operator:
interface Person { name: string age: number } function printName(person?: Person) { console.log(`The name is ${person!.name}`) }
Note that TypeScript has an alternative for referencing attributes and functions on objects that might be null or undefined called the optional chaining operator ?.
. For example, person?.name
or word?.toString()
will return undefined
if the variable is not defined or null.
However, the optional chaining operator ?.
cannot solve the TypeScript errors in our second example, in which we tried to assign the value of a variable type string | undefined
to a variable type string
. Learn more about optional chaining in the last section of this article.
As we’ve seen in our examples, the !
operator is very useful when we would like TypeScript to treat our variable as a solid type. This prevents us from having to handle any null or undefined cases when we are certain there is no such case.
Now that we have seen some examples to gain a better understanding of the TypeScript exclamation mark, let’s look at some popular use cases for this operator.
Let’s imagine we have an array of objects and we want to pick an object with a particular attribute value, like so:
interface Person { name: string age: number sex: string } const people: Person[] = [ { name: 'Gran', age: 70, sex: 'female' }, { name: 'Papa', age: 72, sex: 'male' }, { name: 'Mom', age: 35, sex: 'female' }, { name: 'Dad', age: 38, sex: 'male' } ] const femalePerson = people.find(p => p.sex === 'female')
In our snippet above, TypeScript will define the type of femalePerson
as Person | undefined
because it is possible that people.find
yields no result — in other words, that it will be undefined.
However, if femalePerson
has the type Person | undefined
, we will not be able to pass it as an argument to a function expecting type Person
.
When we are performing lookups on these arrays, we are often confident that they have defined values, and we therefore don’t believe any undefined cases exist. Our !
operator can save us from additional — or unnecessary — null or undefined case handling.
Add the non-null assertion operator, like so:
const femalePerson = people.find(p => p.sex === 'female')!
This would make femalePerson
have the type Person
.
React refs are used to access rendered HTML DOM nodes or React elements. Refs are created using React.createRef<HTMLDivElement>()
and then attached to the element using the ref
attribute.
To use React refs, we access the current attribute, ref.current
. Until the element is rendered, ref.current
could be null
, so it has the following type:
HTMLDivElement | null
To attach an event to ref.current
, we would first have to handle possible null
values. Here is an example:
import React from 'react' const ToggleDisplay = () => { const displayRef = React.createRef<HTMLDivElement>() const toggleDisplay = () => { if (displayRef.current) { displayRef.current.toggleAttribute('hidden') } } return ( <div> <div class="display-panel" ref="displayRef"> <p> some content </p> </div> <button onClick={toggleDisplay}>Toggle Content</button> <div> ) }
In the snippet above, we had to handle a type check of displayRef.current
using an if
statement before calling the toggleAttribute
function.
In most cases, we are sure that if the button onClick
event is triggered, then our elements are already rendered. Therefore, there is no need for a check. This unnecessary check can be eliminated using the !
operator, like so:
const displayRef = React.createRef<HTMLDivElement>() const toggleDisplay = () => displayRef.current!.toggleAttribute('hidden') return ( <div> ...
The !
operator does not change the runtime behavior of your code. If the value you have asserted is not null
or undefined
turns out to actually be null
or undefined
, an error will occur and disrupt the execution of your code.
Remember, the difference between TypeScript and JavaScript is the assertion of types. In JavaScript we do not need or use the !
operator because there is no type strictness.
A JavaScript variable can be instantiated with string
and changed to object
, null
, or number
during the execution of the code. This leaves it up to the developer to handle the different cases.
Using the !
operator takes away TypeScript’s “superpower” of preventing runtime type errors. Therefore, it is not the best practice to use the !
operator.
!
TypeScript’s power over JavaScript is the type safety it provides our code. However, we may sometimes want to disable TypeScript’s strict type checks — for example, for the sake of flexibility or backward compatibility. In such cases, we can use the non-null assertion operator !
.
Though a useful feature, I encourage you to explore safer type assertion methods instead. You can go a step further to prevent use of this operation in your project and with your team by adding the typescript-eslint
package to your project and applying the no-non-null-assertion
lint rule.
You could use optional chaining or type predicates as alternatives to non-null assertions.
Optional chaining is a TypeScript shorthand that allows us easily handle the cases where the variable is defined or not. When the variable is not defined or null, the referenced value defaults to value undefined
. Here’s an example of optional chaining:
interface Person { name: string age: number sex: string } function printName(person?: Person): void { console.log('The name of this person is', person?.name) }
In our example above, if person
is undefined, our print output would be as follows:
'The name of this person is undefined'
Using type predicates in TypeScript is done by defining a function that performs a boolean test and returns a type predicate in the form arg is Type
. Here is an example:
interface Person { name: string age: number sex: string } function validatePerson(person?: Person) person is Person { return !!person }
Using this type predicate, we can first validate the object before performing any further operations, like so:
function printName(person?: Person) { if (!validatePerson(person)) { console.log('Person is invalid') return } console.log(`The name is ${person.name}`) }
!!
in TypeScriptWhile we’re on the topic of the exclamation mark !
, TypeScript also uses double exclamation marks !!
to convert (also called cast) non-Boolean type values to Boolean type. Here’s an example:
const emptyStr = '' const nonEmptyStr = 'test' const emptyStrBool = !!emptyStr //false const nonEmptyStrBool = !!nonEmptyStr //true
In our example above, we converted our string variables to Boolean using the !!
operator. But you might wonder, why do these two strings have different Boolean results? Well the answer is one string (the empty string) is falsy while the other (non-empty string) is truthy. Let’s explore that a little.
Truthy/falsy refers to how values evaluate in a conditional statement regardless of their actual type.
Think about if/else
statements, they evaluate the truthiness or falseness of the expression not its actual value. Here are a few truthy/falsy expressions:
const num1 = 12 const num2 = 26 // expression 1 - falsey if (num1 > num2) { ... } // expression 2 - truthy if (num1) { ... }
So in summary, a falsy expression is one that will evaluate to false
when the expression is converted to a Boolean and is truthy if it evaluates to true
.
N.B., in TypeScript (and in JavaScript too), empty values such as 0
, ''
, undefined
and null
are falsy and will evaluate to false
values when casted to a Boolean.
!!
TypeScript (and JavaScript) lets you convert a non-Boolean value to Boolean using the double exclamation shorthand. This can be useful especially in TypeScript (which has strict type definitions) where you need to return a Boolean value using a non-Boolean result or variables.
TypeScript’s power over JavaScript is the type safety it provides our code. However, we may sometimes want to disable TypeScript’s strict type checks — for example, for the sake of flexibility or backward compatibility. In such cases, we can use the non-null assertion operator.
Though a useful feature, I encourage you to explore safer type assertion methods instead. You can go a step further to prevent use of this operation in your project and with your team by adding the typescript-eslint
package to your project and applying the no-non-null-assertion
lint rule.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
3 Replies to "Understanding the exclamation mark in TypeScript"
Hey! Wouldn’t it be “Question Mark”? Awesome post by the way! =D
Hi. Think somebody has copied your article https://www.handla.it/understanding-the-exclamation-mark-in-typescript/
Hi Santiago,
Thanks for reading LogRocket’s blog and for letting us know about this! We appreciate it.