Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Clark Valberg
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Clark Valberg

The Double-Bang (!!) Operator And A Misunderstanding Of How JavaScript Handles Truthy / Falsy Values

By
Published in Comments (22)

Strictly speaking, there is no "double-bang" operator (or the "double-not" operator) in JavaScript; the (!!) notation is really just two "Not Operators" in a row. In languages, like JavaScript, that support Truthy / Falsy values, the double-bang operator can be used for Boolean type-casting. But, I've noticed a lot of developers using the double-bang operator even when it serves no purpose. I believe this represents a fundamental misunderstanding of how JavaScript handles Truthy / Falsy values. As such, I thought it would be worthwhile to showcase some misuses of the double-bang operator so that we can simplify our JavaScript code and remove the unnecessary noise.

To find fodder for this post, I just searched for "!!" within one of the InVision code repositories that I use most frequently (and where I see this misunderstanding surface quite often).

Double-Bang Operator In if / else if / while Conditions

The most glaring misuse of the double-bang operator in JavaScript is within if, else if, and while conditions. To see what I mean, let's look at a few sanitized examples from the code - I've presented these as pairings of unnecessary conditions followed by the simplified, equivalent versions:

// Unnecessary double-bang.
if ( !! this.props.onKeypress ) {

	this.props.onKeypress( this.getElementContents( target ) );

}

// Simplified version - the expressions within the IF condition are already evaluated
// as Truthy / Falsy values; and, since a Function is inherently a Truthy and a null or
// undefined value is inherently a Falsy, there's no need to explicitly cast to a
// strict Boolean.
if ( this.props.onKeypress ) {

	this.props.onKeypress( this.getElementContents( target ) );

}

// ---

// Unnecessary double-bang.
if ( !! color && ( color.length > 0 ) ) {

	// ...

}

// Simplified version - an empty String is a Falsy value. As such, there's no need to
// differentiate between a null value and an empty string within an IF condition since
// the IF condition is already evaluating Truthy / Falsy values. Similarly, a Number is
// also a Truthy / Falsy value. As such, there's no need to compare the length to zero.
if ( color ) {

	// ...

}

// ---

// Unnecessary double-bang.
if ( !! this.props.className ) {

	className += ( " error-" + this.props.className );

}

// Simplified version - an empty String is a Falsy value. There's no need to cast to a
// strict Boolean within an IF condition.
if ( this.props.className ) {

	className += ( " error-" + this.props.className );

}

// ---

// Unnecessary double-bang.
if ( !! comments && ( comments.length <= 1 ) ) {

	return( false );

}

// Simplified version - "safe navigation" already uses Truthy / Falsy values - there's
// no need to cast an Array to a Boolean simply to know if it is safe to check for the
// Array length.
if ( comments && ( comments.length <= 1 ) ) {

	return( false );

}

// ---

// Unnecessary double-bang.
if ( !! x && !! y ) {

	movePanel( x, y );

}

// Simplified version - since numbers are inherently Truthy / Falsy values, and IF
// conditions work with Truthy / Falsy values, there's no need to cast anything.
if ( x && y ) {

	movePanel( x, y );

}

// ---

// Unnecessary double-bang.
if ( !! e.metaKey && ( e.which === 13 ) ) {

	saveForm();

}

// Simplified version - since IF conditions already work with Truthy / Falsy values,
// there's no need to cast the value to a strict Boolean.
if ( e.metaKey && ( e.which === 13 ) ) {

	saveForm();

}

// ---

// Unnecessary double-bang.
if ( ! this.props.user ) {

	tooltipName = "Add / Remove People";

} else if ( !! this.props.user.email ) {

	tooltipName = this.props.user.email;

} else if ( !! this.props.user.name ) {

	tooltipName = this.props.user.name;

}

// Simplified version - since IF conditions work with Truthy / Falsy values, there's no
// need to cast Strings to Boolean. And, since JavaScript already treats empty-Strings as
// Falsy values, we can simply OR (||) the two Strings together.
if ( this.props.user ) {

	tooltipName = ( this.props.user.email || this.props.user.name );	

} else {

	tooltipName = "Add / Remove People";

}

The fundamental misunderstanding codified in the above if conditions is that JavaScript already treats if conditions as Truthy / Falsy values. As such, there's no need to cast sub-expressions within the conditions to strict Booleans. JavaScript is already doing the heavy-lifting for you - we can remove the double-bang operators, thereby removing unnecessary noise.

Double-Bang Operator In Ternary Expressions

The Ternary operator in JavaScript is really just a short-hand notation for the if / else condition. As such, everything that I just talked about above applies directly to the ternary operator. But, since it uses a different form-factor, it's probably worth seeing a few examples:

// Unnecessary double-bang.
var label = ( !! attributes.buttonLabel ? attributes.buttonLabel : $button.text() );

// Simplified version - since the first portion of the ternary is just an IF condition,
// and IF conditions already deal with Truthy / Falsy values, there's no need to cast to
// a Boolean. And, since the an empty String is inherently a Falsy value and the OR
// operator also deals with Truthy / Falsy values, we can remove the ternary operator
// entirely and just use an OR statement.
var label = ( attributes.buttonLabel || $button.text() );

// ---

// Unnecessary double-bang.
var imageUrl = ( !! user.avatarUrl )
	? user.avatarUrl
	: ( "/avatars/" + user.avatarID )
;

// Simplified version - since .avatarUrl is either null or an empty string, both of which
// are Falsy values, there's no need to cast to a Boolean. And, once more, we can easily
// remove the complex condition with a simplified OR condition.
var imageUrl = ( user.avatarUrl || ( "/avatars/" + user.avatarID ) );

In React, this kind of unnecessary double-bang operator can usually be seen in the JSX code when trying to figure out when to render Components:

// Unnecessary double-bang.
{ !! this.props.isShareable ? <ShareButton /> : null }

// Simplified version - since React won't render Booleans, Null, or Undefined values, we
// can get rid of our ternary operator altogether and just AND the expression with the
// Component that we want to render.
{ this.props.isShareable && <ShareButton /> }

Since React / JSX won't render True, False, Null, or Undefined values, we can actually remove most ternary operators from the JSX code (and, let's be honest, ternary operators read like hot garbage in React JSX).

Double-Bang Operator In Array Filtering

When you execute a .filter() operation on an Array, the return value of the .filter() operator expects a Truthy / Falsy value - not a strict Boolean. As such, there's no need to cast Truthy values to Boolean:

// Unnecessary double-bang.
var activeUsers = users.filter(
	function operator( user ) {

		return( !! user.projectCount );

	}
);

// Simplified version - since the .filter() operator expects a Truthy / Falsy return;
// and, since things like Numbers and Strings are inherently Truthy / Falsy values;
// there's no need to cast the return value - JavaScript is already doing that for you.
var activeUsers = users.filter(
	function operator( user ) {

		return( user.projectCount );

	}
);

The same applies to any Array method (such as .find(), .every(), and .some()) that expects a True / False return - it works perfectly well with a Truthy / Falsy value.

Double-Bang Operator In React cx() / classnames()

One of the common packages from the React user-space is classnames, which shows up in the React JSX code as cx(). This flexible method takes, among other things, an Object that maps CSS class-names onto expressions. The evaluation of these expressions is performed as Truthy / Falsy values. As such, there's no need to cast cx() expressions to strict Booleans:

// Unnecessary double-bang.
var buttonClass = cx({
	"has-image": !! this.props.item.hasImage,
	"dark": !! this.state.darkMode
});

return( <Button className={ buttonClass } /> );

// Simplified version - since the expressions in cx() work with Truthy / Falsy values,
// there's no need to cast our expressions to strict Booleans. Let classnames do the
// heavy lifting for us!
var buttonClass = cx({
	"has-image": this.props.item.hasImage,
	"dark": this.state.darkMode
});

return( <Button className={ buttonClass } /> );

Double-Bang Operator In AngularJS Directives

Everything I just said about the cx() method in React holds-true for the ng-class directive in AngularJS. The ng-class directive takes an Object that maps CSS class-names onto expressions; and, just as with cx(), those expressions are evaluated as Truthy / Falsy values:

<!-- Unnecessary double-bang. -->
<div ng-class="{ mobile: !! prototype.isMobile }">
	{{ prototype.name }}
</div>


<!--
	Simplified version - since the expressions in ng-class work with Truthy / Falsy
	values, there's no need to cast our expressions to strict Booleans. Let Angular
	do the heavy lifting for us!
-->
<div ng-class="{ mobile: prototype.isMobile }">
	{{ prototype.name }}
</div>

In fact, the same holds true for other AngularJS directives like ng-if, ng-show, and ng-hide. Just as with the if condition in JavaScript, the ng-if et al directives also evaluate as Truthy / Falsy values. As such, there's no need to add the noise of the double-bang operator.

So, When Should You Use The Double-Bang Operator In JavaScript?

You should use the double-bang operator in JavaScript when you either need a strict Boolean value; or, when you're passing a value out-of-scope and you don't know if the value will be consumed as a strict Boolean. On the latter point, the "don't know" portion is critical. If you own both the current context and the calling context, then you know how the value will be consumed. As such, it's perfectly legitimate to return Truthy / Falsy values instead of strict Booleans.

Of course, I think you should always try to write the most intuitive, least surprising code. So, if you have variables names like isXYZ or hasXYZ or canXYZ, then those variables should probably reference strict Booleans since the name indicates a Boolean value. Populating such variables with Truthy values may lead to unexpected runtime behaviors.

To be clear, I love the double-bang operator in JavaScript. The point of this post was not to rally against it. All I'm trying to do here is demonstrate that you actually need the double-bang operator far less often than you might think. And there's no need to add unnecessary noise to code that would otherwise be shorter and more intuitive (and more flexible).

Want to use code from this post? Check out the license.

Reader Comments

429 Comments

Hi Ben. Nice exploration...


if ( !! comments && ( comments.length <= 1 ) ) {	
	return( false );
}

To


if ( comments && ! comments.length ) {	
	return( false );
}

I thought numbers equal to 0, are Falsey numbers.

In the above example you are testing for numbers that are less than or equal to 1

So, shouldn't it be:


if ( comments && comments.length <= 1) {	
	return( false );
}

2 Comments

If you want to cast something to boolean, I find Boolean(something) most obvious. And if you want to filter out falsy values from an array, [...].filter(Boolean)

15,811 Comments

@Craig,

Normally, I use it when I am preparing a response to a method call in which I had to calculate a value. For example, I might have something like this:

function hasTeamMembers( team ) {

	return( !! team.members.length );

}

... where any length of .members greater than zero merits a true.

In Angular code, I also use it in things like the ng-switch directive where in I need to compare strict values, like:

<div ng-switch="( !! user.avatarUrl )">
	<span ng-switch-when="true"> ... </span>
	<span ng-switch-when="false"> ... </span>
</div>

Here, I'm casting the avatarUrl which may be a string, an empty string, or a null value, for example, into something that I can explicitly match true/false against.

Honestly, I don't use double-bang all that much on the client-side. I actually use it much more often on the server-side where I need to translate database records into API responses. In that case, I may need to coalesce a value into a true. For example, given a SQL query like:

SELECT
	u.id,
	( a.id ) AS account_id
FROM
	user u
LEFT OUTER JOIN
	account a
ON
	a.userID = u.id

... where the account_id may or may not exist, I would prepare the data-access response like:

return({
	id: data.id,
	hasAccount: !! val( data.account_id )
});

... where I need to cast the potentially empty value in the LEFT OUTER JOIN to a Boolean.

15,811 Comments

@Atmin,

That's an interesting trick, filtering using the Boolean constructor. I don't believe I've seen that before.

Re: clarity, I go back and forth on this. I know that many people use Boolean() and Number() to cast values. And, I tend to use !! for Booleans and + for numbers:

return({
	asBoolean: !! value1,
	asNumber: + value2
});

I go back and forth about whether I think using "operators" is less clear than "constructors".

2 Comments

I think of it not as a "constructor", but as a "type", lack of new helps with that perception. Less punctuation (albeit more characters overall) feels more elegant to me ¯_(ツ)_/¯

1 Comments

@Ben,

I'm going to strenuously object to any use of "!!"; it's simply too Perl-ish. I'd always rather see ambiguous or maybe-not-truthy/falsey expressions cast explicitly, using Boolean(); it's far less likely to be misunderstood by anyone coming along later who has to maintain the code.

("I strenuously object?" Is that how it works? Hm? "Objection." "Overruled." "Oh, no, no, no. No, I STRENUOUSLY object." "Oh. Well, if you strenuously object then I should take some time to reconsider.") - Lt. Weinberg

3 Comments

The comment on jsx is correct except for when dealing with zeros. In this instance, this falsy value will be rendered into html as the character 0

1 Comments

Hi Ben, great article. There is one case when double bang is handy for react and it is
{ !!data.length && <h2>Hello!</h2> }. Prints 0 without the !!.

15,811 Comments

@Mitchell,

Great catch. I don't actually write that much JSX (I mostly use Angular / AngularJS these days). But, I do maintain a lot of other people's JSX :D Which is where I see most of this stuff. For some reason, the !! seems to be more prevalent in our JSX code than our AngularJS code.

15,811 Comments

@Howard,

Honestly, I do go back and forth on this point. A number of people have recommended using Number() instead of + and Boolean() instead of !!. The thing that I keep coming back to is that the context of its use generally implies its usage. For example, when setting an object key like:

var result = {
	hasItems: !! items.length
};

... I always feel like the variable name, hasItems helps to add clarity to any confusion about what it is doing.

But, maybe I just need to start using the Constructor approach a bit more to see how it feels. I might end-up preferring it if I give it a shot.

3 Comments

@Ben,

The reason its in jsx code so much is that you tend to get a little paranoid about things being returned in the jsx when you only want a boolean. For instance if you used an array as a truthy value, and somehow it was returned because you didnt do the condition correctly, or due to some edge case; jsx will render that array. Jsx will also try render and object, and react will error on most objects

3 Comments

@Howard,

Id argue Boolean() is MORE confusing for quick readers as the difference between new Boolean() and Boolean() is tiny, but functionally they are completely different

If (Boolean(false)) returns false
If (new Boolean(false)) returns true

15,811 Comments

@Mitchell,

Oh, that's a really interesting point re: Boolean() as an Object vs. a cast-value. I knew there was a major difference between the two; but, I couldn't quite remember what it was.

1 Comments

// Simplified version - since numbers are inherently Truthy / Falsy values, and IF // conditions work with Truthy / Falsy values, there's no need to cast anything. if ( x && y ) { movePanel( x, y ); }

Except what if you wanted to move the panel to a 0 position? 0 is a number, but it is falsey, not truthy.

I prefer to use actual booleans for boolean operations, and wish Javascript didn't support lazy truthy/falsey conversions at all.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel