We're planting a tree for every job application! Click here to learn more

Thinking JavaScript

Kyle Simpson

2 Mar 2018

•

6 min read

Thinking JavaScript
  • JavaScript

I was teaching a JavaScript workshop the other day and one of the attendees asked me a JS brain teaser during the lunch break that really got me thinking. His claim was that he ran across it accidentally, but I'm a bit skeptical; it might just have been an intentional WTF trick!

Anyway, I got it wrong the first couple of times I was trying to analyze it, and I had to run the code through a parser then consult the spec (and a JS guru!) to figure out what was going on. Since I learned some things in the process, I figured I'd share it with you.

Not that I expect you'll ever intentionally write (or read, hopefully!) code like this, but being able to think more like JavaScript does always help you write better code.

The Setup

The question posed to me was this: why does this first line "work" (compiles/runs) but the second line gives an error?

[[]][0]++;

[]++;

The thinking behind this riddle is that [[]][0] should be the same as [], so either both should work or both should fail.

My first answer, after thinking about it for a few moments, was that these should both fail, but for different reasons. I was incorrect on several accounts. Indeed, the first one is valid (even if quite silly).

I was wrong despite the fact that I was trying to think like JavaScript does. Unfortunately, my thinking was messed up. No matter how much you know, you can still easily realize you don't know stuff.

That's exactly why I challenge people to admit: "You Don't Know JS"; none of us ever fully knows something as complex as a programming language like JS. We learn some parts, and then learn more, and keep on learning. It's a forever process, not a destination.

My Mistakes

First, I saw the two ++ operator usages, and my instinct was that these will both fail because the unary postfix ++, like in x++, is mostly equivalent to x = x + 1, which means the x (whatever it is) has to be valid as something that can show up on the left-hand side of an = assignment.

Actually, that last part is true, I was right about that, but for the wrong reasons.

What I wrongly thought was that x++ is sorta like x = x + 1; in that thinking, []++ being [] = [] + 1 would be invalid. While that definitely looks weird, it's actually quite OK. In ES6, the [] = .. part is valid array destructuring.

Thinking of x++ as x = x + 1 is misguided, lazy thinking, and I shouldn't be surprised that it led me astray.

Moreover, I was thinking of the first line all wrong, as well. What I thought was, the [[]] is making an array (the outer [ ]), and then the inner [] is trying to be a property access, which means it gets stringified (to ""), so it's like [""]. This is nonsense. I dunno why my brain was messed up here.

Of course, for the outer [ ] to be an array being accessed, it'd need to be like x[[]] where x is the thing being accessed, not just [[]] by itself. Anyway, thinking all wrong. Silly me.

Corrected Thinking

Let's start with the easiest correction to thinking. Why is []++ invalid?

To get the real answer, we should go to the official source of authority on such topics, the spec!

In spec-speak, the ++ in x++ is a type of ["Update Expression"](http://www.ecma-international.org/ecma-262/8.0# sec-update-expressions) called the ["Postfix Increment Operator"](http://www.ecma-international.org/ecma-262/8.0# sec-postfix-increment-operator). It requires the x part to be a valid ["Left-Hand Side Expression"](http://www.ecma-international.org/ecma-262/8.0# sec-left-hand-side-expressions) -- in a loose sense, an expression that is valid on the left-hand side of an =. Actually, the more accurate way to think of it is not left-hand side of an =, but rather, valid target of an assignment.

Looking at the list of valid expressions that can be targets of an assignment, we see things like ["Primary Expression"](http://www.ecma-international.org/ecma-262/8.0# prod-PrimaryExpression) and ["Member Expression"](http://www.ecma-international.org/ecma-262/8.0# prod-MemberExpression), among others.

If you look into Primary Expression, you find that an ["Array Literal"](http://www.ecma-international.org/ecma-262/8.0# prod-ArrayLiteral) (like our []!) is valid, at least from a syntax perspective.

So, wait! [] can be a left-hand side expression, and is thus valid to show up next to a ++. Hmmm. Why then does []++ give an error?

What you might miss, which I did, is: it's not a SyntaxError at all! It's a runtime error called ReferenceError.

Occassionally, I have people ask me about another perplexing -- and totally related! -- result in JS, that this code is valid syntax (but still fails at runtime):

2 = 3;

Obviously, a number literal shouldn't be something we can assign to. That makes no sense.

But it's not invalid syntax. It's just invalid runtime logic.

So what part of the spec makes 2 = 3 fail? The same reason that 2 = 3 fails is the same reason that []++ fails.

Both of these operations use an abstract algorithm in the spec called ["PutValue"](http://www.ecma-international.org/ecma-262/8.0# sec-putvalue). Step 3 of this algorithm says:

If Type(V) is not Reference, throw a ReferenceError exception.

[Reference is a special specification type](http://www.ecma-international.org/ecma-262/8.0# sec-reference-specification-type) that refers to any kind of expression that represents an area in memory where some value could be assigned. In other words, to be a valid target, you have to be a Reference.

Clearly, 2 and [] are not References, so that's why at runtime, you get a ReferenceError; they are not valid assignment targets.

But What About...?

Don't worry, I haven't forgotten the first line of the snippet, which works. Remember, my thinking was all wrong about it, so I've got some correcting to do.

[[]] by itself is not an array access at all. It's just an array value that happens to contain another array value as its only contents. Think of it like this:

var a = [];
var b = [a];

b;  // [[]]

See?

So now, [[]][0], what is that all about? Again, let's break it down with some temporary variables.

var a = [];
var b = [a];

var c = b[0];
c;  // [] -- aka, `a`!

So the original setup premise is correct. [[]][0] is kinda the same as just [] itself.

Back to that original question: why then does line 1 work but line 2 doesn't?

As we observed earlier, the ["Update Expression"](http://www.ecma-international.org/ecma-262/8.0# sec-update-expressions) requires a ["LeftHandSideExpression"](http://www.ecma-international.org/ecma-262/8.0# prod-LeftHandSideExpression). One of the valid types of those expressions is ["Member Expression"](http://www.ecma-international.org/ecma-262/8.0# prod-MemberExpression), like [0] in x[0] -- that's a member expression!

Look familiar? [[]][0] is a member expression.

So, we're good on syntax. [[]][0]++ is valid.

But, wait! Wait! Wait!

If [] is not a Reference, how could [[]][0] -- which results in just [], remember! -- possibly be considered a Reference so that PutValue(..) (described above) doesn't throw an error?

This is where things get just a teeny bit tricky. Hat tip to my friend Allen-Wirfs Brock, former editor of the JS spec, for helping me connect the dots.

The result of a member expression is not the value itself ([]), but rather a Reference to that value -- see [Step 8 here](https://tc39.github.io/ecma262# sec-property-accessors-runtime-semantics-evaluation). So in fact, the [0] access is giving us a reference to the 0th position of that outer array, rather than giving us the actual value in that position.

And that's why it's valid to use [[]][0] as a left hand side expression: it actually is a Reference after all!

As a matter of fact, the ++ does actually update the value, as we can see if we were to capture these values and inspect them later:

var a = [[]];
a[0]++;

a;  // [1]

The a[0] member expression gives us the [] array, and the ++ as a mathematical expression will coerce it to a primitive number, which is first "" and then 0. The ++ then increments that to 1 and assigns it to a[0]. It's as if a[0]++ was actually a[0] = a[0] + 1.

A little side note: if you run [[]][0]++ in the console of your browser, it will report 0, not 1 or [1]. Why?

Because ++ returns the "original" value (well, after coercion, anyway -- see [step 2 here](https://tc39.github.io/ecma262# sec-postfix-increment-operator-runtime-semantics-evaluation)), not the updated value. So the 0 comes back, and the 1 gets put into the array via that Reference.

Of course, if you don't keep the outer array in a variable like we did, that update is moot since the value itself goes away. But it was updated, nonetheless. Reference. Cool!

Post-Corrected

I don't know if you appreciate JS or feel frustrated by all these nuances. But this endeavor makes me respect the language more, and renews my vigor to keep learning it even deeper. I think any programming language will have its nooks and crannies, some of which we like and some which drive us mad!

No matter your persuasion, there should be no disagreement that whatever your tool of choice, thinking more like the tool makes you better at using that tool. Happy JavaScript thinking!

If you're interested in working with JavaScript, check out our JavaScript Works job-board here.

Did you like this article?

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub