DEV Community

Cover image for JavaScript: Not-Not (!!) Anti-Pattern
bob.ts
bob.ts

Posted on

JavaScript: Not-Not (!!) Anti-Pattern

The Not-Not Pattern (!!), or bang-bang, is a way in JavaScript to do a type conversion.

! means NOT. So ...

  • !true is false
  • !false is true
  • !0 is true
  • !1 is false

So when converting a value to a boolean, the code inverts it and inverts it again.

Examples

          !!false === false
           !!true === true

              !!0 === false
!!parseInt("foo") === false // NaN is falsy
              !!1 === true
             !!-1 === true  // -1 is truthy
          !!(1/0) === true  // Infinity is truthy

             !!"" === false // empty string is falsy
          !!"foo" === true  // non-empty string is truthy
        !!"false" === true  // even if it contains a falsy value

     !!window.foo === false // undefined value is falsy
      !!undefined === false // undefined primitive is falsy
           !!null === false // null is falsy

             !!{} === true  // an (empty) object is truthy
             !![] === true  // an (empty) array is truthy
Enter fullscreen mode Exit fullscreen mode

In the Real World

If those examples weren't enough to get you to think about this pattern differently, you should know that this pattern can have some significant and unexpected side-effects.

A codebase I was working in loaded information on images on the page. We had a process that needed to run if an image was returned. The thought behind this code was that it should not run if there was an error.

Six developers and months of time were spent trying to track down a bug. This bug showed up one in every 3,000 times the information on an image was loaded.

The Code

Patience and persistence on the part of an intern revealed a significant side effect.

const image = { name: 'BOB.jpg' };

function checkImage(image) {
  if (!!image) {
    console.log('image exists');
  }
}
checkImage(image);
Enter fullscreen mode Exit fullscreen mode

So, the original code looked as you see above. an image object was passed in and if it exists, the console.log is run.

Unexpected Side-Effect

Here's where the side-effect came in ...

const image = "ERROR: Authentication Wrong";

function checkImage(image) {
  if (!!image) {
    console.log('image exists');
  }
}
checkImage(image);
Enter fullscreen mode Exit fullscreen mode

With the code above, the intern saw that when an error occurred, the Not-Not Pattern saw the error message at true and it tried to process as if there was an image.

A Solution

The solution wasn't challenging.

We needed a better pattern for checking the image object. This is one solution; there can be many more.

const image = "ERROR: Authentication Wrong";

function isObject(val) {
  if (val === null) { return false;}
  return ( (typeof val === 'function') || (typeof val === 'object') );
}

function checkImage(image) {
  if (isObject(image) === true) {
    console.log('image exists');
  }
}
checkImage(image);
Enter fullscreen mode Exit fullscreen mode

With the code above, the console.log does not run.

Summary

The Not-Not Pattern (!!), or bang-bang, is a way in JavaScript to do a type conversion. This pattern can have some significant and unexpected side-effects.

Because of these side effects I am reluctant to use the pattern in most cases and write code that is longer, with more clarity of purpose, and less potential side-effects.

Top comments (2)

Collapse
 
punund profile image
punund

What real anti-pattern in your code is mixing types, and this has nothing to to with !!. Your function may receive an Object, a String, or a null, and had no notion of it. Another anti-pattern is typeof for such cases.

This is solved with anything from the Maybe monad to typing your data correctly:

const data1 = { image: 'bob.jpeg', error: null }
const data2 = { image: null, error: 'Bob was not found' }
const data3 = { image: null, error: 'Wrong authentication' }
Enter fullscreen mode Exit fullscreen mode

In the real word, !! is not needed for other reasons.
!! converts from "falsy" to false, but it has no value by itself, as any JS context expecting a boolean, does it for you:

// if and ternary
if(2) 'this is true'
if('') 'this is false'

[] ? 'any array is true' : new Error('must never happen')
0 ? 'zero is not true' : 'it is false'
Enter fullscreen mode Exit fullscreen mode
// logical operators
2 && true // ==  true
0 || 1 || 2 //  == 1, obviously
true && {} == true
Enter fullscreen mode Exit fullscreen mode

So in your original code if(image) is strictly equivalent to if(!!image).

Collapse
 
radulle profile image
Nikola Radulaški

TS would have helped here a lot.