1458

When looking at the source code for a tslint rule, I came across the following statement:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Notice the ! operator after node.parent. Interesting!

I first tried compiling the file locally with my currently installed version of TS (1.5.3). The resulting error pointed to the exact location of the bang:

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.

Next, I upgraded to the latest TS (2.1.6), which compiled it without issue. So it seems to be a feature of TS 2.x. But, the transpilation ignored the bang completely, resulting in the following JS:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

My Google fu has thus far failed me.

What is TS's exclamation mark operator, and how does it work?

1
  • 4
    You have a lazy developer in your team ^^; Your IDE would give you an error message if there is no type definition, or the prop may be undefined in runtime. ! will bypass this error message, which is something like: "I tell you, it will be there." Commented Jul 9, 2022 at 11:15

9 Answers 9

2007

That's the non-null assertion operator. It is a way to tell the compiler "this expression cannot be null or undefined here, so don't complain about the possibility of it being null or undefined." Sometimes the type checker is unable to make that determination itself.

It is explained in the TypeScript release notes:

A new ! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded. Similar to type assertions of the forms <T>x and x as T, the ! non-null assertion operator is simply removed in the emitted JavaScript code.

I find the use of the term "assert" a bit misleading in that explanation. It is "assert" in the sense that the developer is asserting it, not in the sense that a test is going to be performed. The last line indeed indicates that it results in no JavaScript code being emitted.

Sign up to request clarification or add additional context in comments.

10 Comments

Good explanation. I find it a good practice to do a console.assert() on the variable in question before appending a ! after it. Because add ! is telling the compiler to ignore the null check, it compiles to noop in javascript. So if you are not sure that the variable is non-null, then better do an explicit assert check.
As a motivating example: using the new ES Map type with code like dict.has(key) ? dict.get(key) : 'default'; the TS compiler can't infer that the get call never returns null/undefined. dict.has(key) ? dict.get(key)! : 'default'; narrows the type correctly.
Is there slang for this operator, like how the Elvis operator refers to the binary operator?
@ebakunin "The bang operator", as you can see below in Mike's answer
@ebakunin, Elvis ?. AFAIK came from the C# land. With nullable types, C# got its bang too (pun, of course, intended). Yup, the Sir Tony's invention wroke a serious havoc on the world of procedural programming, and we still cleaning the fallout. Being the sweetest person, he still apologizes for it. Curiously, his major contribs to CS are in automatic reasoning about program correctness (e.g., Hoare logic), applied in static code analysis: he invented both the null and the ways to statically catch it! :)
|
468

Louis' answer is great, but I thought I would try to sum it up succinctly:

The bang operator tells the compiler to temporarily relax the "not null" constraint that it might otherwise demand. It says to the compiler: "As the developer, I know better than you that this variable cannot be null right now".

11 Comments

Or, as the compiler, it has messed up. If the constructor does not initialize a property but a lifecycle hook does it and the compiler does not recognize this.
This is not the responsibility of the TS compiler. Unlike some other languages (eg. C#), JS (and therefore TS) does not demand that variables are initialized before use. Or, to look at it another way, in JS all variables declared with var or let are implicitly initialized to undefined. Further, class instance properties can be declared as such, so class C { constructor() { this.myVar = undefined; } } is perfectly legal. Finally, lifecycle hooks are framework dependent; for instance Angular and React implement them differently. So the TS compiler cannot be expected to reason about them.
@EugeneKarataev Yes there is, frameworks often initialize variables inside themselves and the ts control flow analysis can’t catch it. It’s usage is certainly reduced, but you will come across instances where you need it.
@EugeneKarataev readability for one thing. The exclamation sign tells the reader of the code: THIS CANNOT BE NULL. (sorry about caps). While ? says: this might be null, which is not true (therefore you can only use ! if you ABSOLUTELY know it's not null.
@EugeneKarataev the difference is that ?. return type is nullable (even if it can't ever happen), and you'll have to deal with a nullable type down the road. While if you know null is impossible, !. fixes the type once and for all.
|
98

Non-null assertion operator

With the non-null assertion operator we can tell the compiler explicitly that an expression has value other than null or undefined. This is can be useful when the compiler cannot infer the type with certainty but we have more information than the compiler.

Example

TS code

function simpleExample(nullableArg: number | undefined | null) {
   const normal: number = nullableArg; 
    //   Compile err: 
    //   Type 'number | null | undefined' is not assignable to type 'number'.
    //   Type 'undefined' is not assignable to type 'number'.(2322)

   const operatorApplied: number = nullableArg!; 
    // compiles fine because we tell compiler that null | undefined are excluded 
}

Compiled JS code

Note that the JS does not know the concept of the Non-null assertion operator since this is a TS feature

"use strict";
function simpleExample(nullableArg) {
    const normal = nullableArg;
    const operatorApplied = nullableArg;
}

Comments

47

Short Answer

Non-null assertion operator (!) helps the compiler that I'm sure this variable is not a null or undefined variable.

let obj: { field: SampleType } | null | undefined;

... // some code

// the type of sampleVar is SampleType
let sampleVar = obj!.field; // we tell compiler we are sure obj is not null & not undefined so the type of sampleVar is SampleType

Comments

9

My understanding is the ! operator do the same thing like NonNullable.

let ns: string | null = ''
//  ^? let ns: string | null
let s1 = ns!
//  ^? let s1: string
let s2 = ns as NonNullable<typeof ns>
//  ^? let s2: string

Comments

7

Non-Nullable TypeScript performs strict null checks to help catch potential null or undefined errors. When you try to access a member (property or method) on a variable that could be null or undefined, TypeScript raises a compilation error.

let myElement: HTMLElement | null = document.getElementById('myElement');

// Without non-null assertion operator
// Compiler error: Object is possibly 'null'.
myElement.innerHTML = 'Hello, world!';

// With non-null assertion operator
myElement!.innerHTML = 'Hello, world!';

Comments

6

TS's exclamation mark operator: It's used to set the not-nullable references. It tells the typescript compiler that the variable can't be Null or undefined. Please check the following example.

let referenceA: string | null = null
const n = 1
if (n) {
    referenceA= "Hello My World!"    
}
console.log(referenceA.toLowerCase()) // Error: Object is possibly 'null'.ts(2531)

To avoid that error we need to tell the compiler that the variable can't be Null using the "!" operator, which's called non-null assertion operator.

    let referenceA: string | null = null
    const n = 1
    if (n) {
        referenceA= "Hello My World!"    
    }
    console.log(referenceA!.toLowerCase())

Comments

1

The ! you’re seeing after node.parent is not a logical NOT. It is the non-null assertion operator, introduced in TypeScript 2.0.

What it does

The non-null assertion operator tells the TypeScript compiler:

“I know this value is not null or undefined at runtime. trust me.”

node.parent!.kind

is TypeScript’s way of asserting that node.parent is definitely not null or undefined, allowing access to .kind without compiler errors.

Why it compiles away

This operator is purely a compile-time assertion. It does nothing at runtime, so when TypeScript outputs JavaScript, the ! is simply removed:

TypeScript:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

JavaScript output:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Why your older TypeScript version failed

You mentioned:

TypeScript 1.5.3 → syntax error

TypeScript 2.1.6 → compiles correctly

That’s expected — the non-null assertion operator was introduced in TS 2.0. Versions before that don’t understand the syntax and throw an error.

When to use it

Use ! when TypeScript’s control flow analysis can’t guarantee non-null values, but you know the value won’t be null:

const element = document.getElementById("app")!;
element.innerText = "Hello!";

When not to use it

Don’t use it blindly. It bypasses safety checks, and if you're wrong, you’ll get a runtime exception.

Comments

0

Most answers so far are incomplete, if not slightly incorrect in their generalized tone.

Indeed, it is one use case to flag values as not null and not undefined.

But what the bang operator generally does is, it turns off errors of TypeScript code when compiling it to JavaScript code. It tells TypeScript to leave the expressions result as it is and pass it to JavaScript.

It allows the use of JavaScript semantics in TypeScript, such as using loose equality (with the convenience of omitting all the checks) or using the (loose) inequality comparisons. It can improve the efficiency of the code for people who know how JavaScript works or who want to refactor JavaScript code into TypeScript code without altering behaviour.

backlog.addEventListener('message', (event: BacklogEvent) =>
{
    if (event.target.currentTime! < workday?.endTime!)
    {
        coworker.pushWork(backlog.pop());
    }
});

This "lazy" code snippet will simply only trigger the if-block, if the left side is a valid date that is before the valid date on the right side, even if they can also be undefined in the weekend or after the work day; just in case your coworker crunches extra hours. (Note that nulls are treated as 0 in JavaScript comparisons <, <=, > and >=! You can easily check that yourself.)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.