The Double-Bang (!!) Operator And A Misunderstanding Of How JavaScript Handles Truthy / Falsy Values
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).
if
/ else if
/ while
Conditions
Double-Bang Operator In 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.
cx()
/ classnames()
Double-Bang Operator In React 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
Hi Ben. Nice exploration...
To
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:
@Charles,
Ahhh, good catch. I wasn't actually testing any of these - I'll make that change.
@Charles,
Consider it fixed -- thanks for catching that!
No worries...
Can you provide a couple of specific examples of when you do use the double bang operator?
I've never seen !! missused
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)
@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:
... where any length of
.members
greater than zero merits atrue
.In Angular code, I also use it in things like the
ng-switch
directive where in I need to compare strict values, like:Here, I'm casting the
avatarUrl
which may be a string, an empty string, or anull
value, for example, into something that I can explicitly matchtrue
/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:... where the
account_id
may or may not exist, I would prepare the data-access response like:... where I need to cast the potentially empty value in the
LEFT OUTER JOIN
to a Boolean.@Clement,
That's awesome! Perhaps this is a sign of how old my code-bases are.
@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()
andNumber()
to cast values. And, I tend to use!!
for Booleans and+
for numbers:I go back and forth about whether I think using "operators" is less clear than "constructors".
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 ¯_(ツ)_/¯@Ben, Thanks!
@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
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
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 !!.@Marian,
Good use-case, that makes sense to me. nice tip.
@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.@Howard,
Honestly, I do go back and forth on this point. A number of people have recommended using
Number()
instead of+
andBoolean()
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:... 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.
@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
@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
@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.// 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.