loading...
Cover image for Why (! + [] + [] + ![]).length is 9

Why (! + [] + [] + ![]).length is 9

tomasforsman profile image Tomas Forsman ・2 min read

Edit: At the bottom I've added what I've figured out since I wrote this.

Today I sat down and figured out why you get this result in JavaScript

(! + [] + [] + ![]).length
> 9

So I sat down with the js console and tried to figure things out. There were a lot of this going on:

(!"")
>true

(![])
>false

(!+[])
>true

(!+[]+[])
>"true"

(+[]+"2")
>"02"

([]+"2")
>"2"

What I have come up with, and I might be totally off on some of these so please correct anything that doesn't look right, is the following.

  • JavaScript does operations from right to left.
  • The + operand first tries to add the left to the right, if that isn't possible it tries to convert the operand on the right to a number and if it can't do that it converts it to a string. If there is something to the left of the + it adds them together, and if it can't it sticks around. If there is nothing to the left it goes away.
  • The ! operand converts the operand to the right to a boolean and then reverts it. Only the following values are the same as false as booleans:
false
0
""
null
NaN
undefined

All other values is not false and thus true.

So now it goes something like this:

// We start with
(! + [] + [] + ![]).length  // Remember that we are going from right to left starting 
                            // with the operation before doing -length
![] === false               // ! converts the content of [] to true, since it's not part of 
                            // the false group above, and then reverts it.
+ === +                     // the + is lonely so far with no operand to the right but sticks 
                            // around since it has something on the left.
[]+[] === ""                // The contents of [] can't be added or converted to a number so 
                            // the right operand becomes a string. There's still something to 
                            // the left so the + stays.
!+[] === true               // This the same as !+0 since +[] is 0


// Now we got: 
("false" + "" + false).length
""+false === "false"        // + adds "" to false. It then sticks around.
"true" + "" === "true"      // The + stays after adding "true" to ""


// ---
("true"++"false") 
+"false" ==== "false"       // + has nothing but an operand to the left so it goes away.
true + "" === "true"        // + adds them together and stays

// ---
("true"+"false")
"true" + "false" === "truefalse" // + still stays

// ---
(+"truefalse")
+"truefalse" === "truefalse"     // + has nothing to do after this so it goes away

// ---
("truefalse")               // The operation is done so now we are left with what's outside.

"truefalse".length === 9

Yes, I did go through every step, even those that seems pointless. I'm not at all sure this is how it works but it is what seems to happen to me.

Thoughts?

Edit:
After comments and looking at the documentation this is now how I figure things going.
((!(+[]))+[]+(![]))
Unary operators are going right to left thus !+[] becomes +[] -> !0 === true.

Discussion

pic
Editor guide
Collapse
tpenguinltg profile image
tPenguinLTG

JavaScript does operations from right to left.

In some cases it does, but not in this case. The reason you see behaviour that may appear to be right-to-left evaluation is because of operator precedence, where the unary ! has a higher precedence than the binary +.

+ actually evaluates from left to right, so more verbosely parenthesized, it looks like this:

(((!(+[])) + []) + (![])).length

Evaluating this parenthesized form step-by-step, we get this:

/*  1. */ (((!(+[])) + []) + (![])).length  // parethesized form.
/*  2. */ (((!0) + []) + (![])).length      // `+[]` is evaluated to `0`.
/*  3. */ (((!false) + []) + (![])).length  // `0` is coerced to the boolean `false`.
/*  4. */ ((true + []) + (![])).length      // `!false` is evaluated to `true`.
/*  5. */ (("true" + "") + (![])).length    // `[]` is coerced as a primitive to the string `""`, which means `true` is also coerced to a string.
/*  6. */ ("true" + (![])).length           // `"true" + ""` is string concatenation, which evaluates to `"true"`.
/*  7. */ ("true" + (!true)).length         // `[]` is coerced to the boolean `true`.
/*  8. */ ("true" + false).length           // `!true` evaluates to `false`.
/*  9. */ ("true" + "false").length         // `false` is coerced to a string because `"true"` is a string.
/* 10. */ "truefalse".length                // `"true" + "false"` is evaluated as string concatenation to `"truefalse".
/* 11. */ 9                                 // the length of `"truefalse"` is `9`.
Collapse
tomasforsman profile image
Tomas Forsman Author

The first + is unary, right?

Collapse
tpenguinltg profile image
tPenguinLTG

Indeed it is.

Collapse
karataev profile image
Eugene Karataev

If I'm not sure what's going on with the JS code, I use ASTexplorer to look at the code with the compiler's eyes. It converts a code string into the tree of instructions to be executed by the compiler step by step.
But sometimes text representation of a tree is not expressive enough, so I built a little tool which visualizes an AST tree.
For (! + [] + [] + ![]).length AST in graph form looks like this:
ast
In runtime calculations starts from the bottom left and flow to the top.

Collapse
tomasforsman profile image
Collapse
jacobmparis profile image
Jacob Paris
(({})[[]]+[]).length

This is also equal to 9 but for a very different reason

Collapse
tyskie profile image
tyskie
(-1/0+[]).length

This one too but for yet another reason

Collapse
moopet profile image
Ben Sinclair
(({})[[]]+[])[1]+(({})[[]]+[])[5]+(({})[[]]+[])[1]+(({})[[]]+[])[3]

Standing on the shoulders of giants.

Thread Thread
jacobmparis profile image
Jacob Paris

You can stand on your own shoulders this was perfect

Collapse
kassem profile image
Kassem

Interesting breakdown. I might have missed something but I think "truefalls" should be "truefalse".

Collapse
tomasforsman profile image
Tomas Forsman Author

Indeed it should =D That's dyslexia for ya.

Collapse
tadman profile image
Scott Tadman

Ain't that the truef.

Collapse
sagarb3 profile image
Sagar Bhattacharya

It is a great observation , typecasting in JS is weird. This is the reason I love this platform.

Collapse
alexluong profile image
Alex Luong

AWESOME INTERVIEW QUESTION!!!

Collapse
aerosboss profile image
aerosboss

jajaja many brains will explode

Collapse
nabbisen profile image
Heddi Nabbisen

Totally greatly interesting.
Thank you for your great post.
The behaviors seem a bit strange and are beautiful 😆

Collapse
vocab_king profile image
WhistlerIAM

Hi.
This is very well documented in the Boolean coercion abstract operation by ECMAScript language.
So, there are two operations that you may check out.

  1. ToBoolean and ToString.
Collapse
acido_binario profile image
🍝 sadder sCrypt Kiddie 𖤐🕷️

Is this ease of programming?

Collapse
copy_pasta_chef profile image
Essien

Does this have anything to do with the esoteric version of JS? en.m.wikipedia.org/wiki/JSFuck