Triplebyte Blog

We help engineers join great companies
Try our coding quiz

Share this article

6 'Strange Things' in JavaScript, Explained

By Juan Cruz Martinez on Jul 17, 2020

6 'Strange Things' in JavaScript, Explained

This article was originally published on Live Code Stream by Juan Cruz Martinez (twitter: @bajcmartinez), founder and publisher of Live Code Stream, entrepreneur, developer, author, speaker, and doer of things.

If you work long enough in JavaScript, you're guaranteed to come across some very strange behaviors.

Nobody normal ever accomplished anything meaningful in this world. — Jonathan, Stranger Things

Oh course, even though JavaScript may occasionally throw surprises at you, it's a very powerful language that's worthy of some investigation of its mysteries. So in this article, I will explain some of its strange code snippets so that you can add their behaviors to your toolbox. This language can be a weirdo, but we love it!

Scenario #1: [‘1’, ‘7’, ‘11’].map(parseInt)

Let's take a look at the code for our first scenario

['1', '7', '11'].map(parseInt);

For what you would expect the output to be:

[1, 7, 11]

However, things get a bit off here and the actual result is:

[1,NaN,3]

At first, this may look very weird, but it actually has an elegant explanation. To understand what is going on, we need to understand the two involved functions, map and parseInt.

map()

map() calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values (including undefined).

Now the callback function referenced above will receive some particular parameters. Let’s take a look at an example with its output:

[1, 2, 3].map(console.log)
1 0 > (3) [1, 2, 3]
2 1 > (3) [1, 2, 3]
3 2 > (3) [1, 2, 3]

As we can see, the map function not only did pass the value of the item but also the index and a copy of the full array on each iteration. This is important and is in part what’s affecting our previous result.

parseInt()

The parseInt() function parses a string argument and returns an integer of the specified radix (the base in mathematical numeral systems).

So now, by definition, parseInt(string [, radix]) expects two parameters: the string we want to parse and the radix.

Resolving the mystery

Now that we know enough about the two functions, let’s try to understand what is happening in our case. We will start with our original script and we will explain it step-by-step:

['1', '7', '11'].map(parseInt);

As we know, the callback for the map function will receive three arguments, so let’s do that:

['1', '7', '11'].map((currentValue, index, array) => parseInt(currentValue, index, array));

Starting to get an idea of what happened? When we add the arguments, it comes clear that the parseInt function is receiving additional parameters and not only the actual value of the item in the array. So now we can test what the function would do for each of these value combinations, but we can also ignore the array parameter as it will be discarded by the parseInt function:

parseInt('1', 0)
1
parseInt('7', 1)
NaN
parseInt('11', 2)
3

So that now explains the values we saw initially. The parseInt function result is being altered by the redix parameter which determines the base for the conversion.

Is there a way to get the originally expected result?

Now that we know how it works, we can easily fix our script and get the desired result:

['1', '7', '11'].map((currentValue) => parseInt(currentValue));
> (3) [1, 7, 11]

Scenario #2: (‘b'+'a'+ + ‘a’ + ‘a’).toLowerCase() === ‘banana’

You may be thinking that the expression above is false. After all, there is no letter ‘n’ in the string we are building on the left side of the expression, or isn’t it?

Let’s find out:

('b'+'a'+ + 'a' + 'a').toLowerCase() === 'banana'
true

Ok, you probably realized already what is going on, but if not let me quickly explain it here. Let’s focus on the left side of the expression. There’s nothing weird on the right side, believe me.

('b'+'a'+ + 'a' + 'a').toLowerCase()
"banana"

Somehow, we're forming the word ‘banana’. Let’s remove the lower case conversion and see what happens:

('b'+'a'+ + 'a' + 'a')
"baNaNa"

Bingo! We found some ‘N’ now, and looks like we actually found a NaN inside the string. Perhaps it’s coming from the + + expression. Let’s pretend that and see what we would get:

b + a + NaN + a + a

Hmm, we have an extra a, so let’s try something else:

+ + 'a'
NaN

Ahh there we go… the + + operation by itself is not evaluating, but when we add the character ‘a’ at the end, it all goes into NaN, and now fits into our code. The NaN expression is then concatenated as a string with the rest of the text and we finally get banana. Pretty weird!

Scenario #3: Can’t even name it

(![] + [])[+[]] +
  (![] + [])[+!+[]] +
  ([![]] + [][[]])[+!+[] + [+[]]] +
  (![] + [])[!+[] + !+[]] === 'fail'

What in the world? How does a bunch of brackets form the word fail? (And no, JS is not failing. We are actually getting the string fail as the output.)

Let’s try to explain it. There are a few things in that bunch that form a pattern:

(![] + [])

That pattern evaluates to the string false, which is strange, but its a property of the language. Turns out that the false + [] === 'false' transformation has to do with how JS internally maps the calls. We won’t get into the details as to why this exactly happens.

Once you form the string false the rest is easy. Just look for the positions of the letters that you need — except for one case, the letter i, which is not part of the word false.

For a variation, let’s look at it ([![]] + [][[]]), which evaluates to the string falseundefined. So basically we force an undefined value and concatenate it to the false string.

Loving it so far? Let’s do some more.

Scenario #4: To be truthy or to be true, that is the question.

To be truthy or to be true, that is the question. To be falsy or to be false, that is the question.

What is truthy and falsy? And why are they different from true or false?

Every value in JavaScript has its own boolean value (truthy/falsy). These values are used in operations where a boolean is expected but not given. It's very likely you've once done something like this:

const array = [];
if (array) {
  console.log('Truthy!');
}

In the code above, array is not a boolean even though the value is “truthy” and the expression will result in executing the console.log below.

How do I know what is truthy and what is falsy?

Everything that is not falsy is truthy. Terrible explanation? Fair enough, let’s examine it further.

Falsy values care values with an inherit boolean false. For example, values like:

  • 0
  • -0
  • 0n
  • '’ or "”
  • null
  • undefined
  • NaN
  • Everything else would be truthy.

    Scenario #5: Array equality

    Some things in JS are simply weird. It's the way the language is design and we accept it the way it is. Let’s see some weird array equalities:

    [] == ''   // -> true
    [] == 0    // -> true
    [''] == '' // -> true
    [0] == 0   // -> true
    [0] == ''  // -> false
    [''] == 0  // -> true
    
    [null] == ''      // true
    [null] == 0       // true
    [undefined] == '' // true
    [undefined] == 0  // true
    
    [[]] == 0  // true
    [[]] == '' // true
    
    [[[[[[]]]]]] == '' // true
    [[[[[[]]]]]] == 0  // true
    
    [[[[[[ null ]]]]]] == 0  // true
    [[[[[[ null ]]]]]] == '' // true
    
    [[[[[[ undefined ]]]]]] == 0  // true
    [[[[[[ undefined ]]]]]] == '' // true
    

    If you are interested in why all of the above ... is, you can read more about that in section 7.2.13 Abstract Equality Comparison of the specification. Though I have to warn you that it's not for the easily startled :p

    Scenario #6: Math is Math, unless….

    In our real world, we know that math is math and we know how it works. We were taught since childhood how to add numbers and that whenever you sum the same two numbers together you will get the same result, right? Well… for JavaScript this is not always true… or kind of… let’s see it:

    3  - 1  // -> 2
     3  + 1  // -> 4
    '3' - 1  // -> 2
    '3' + 1  // -> '31'
    
    '' + '' // -> ''
    [] + [] // -> ''
    {} + [] // -> 0
    [] + {} // -> '[object Object]'
    {} + {} // -> '[object Object][object Object]'
    
    '222' - -'111' // -> 333
    
    [4] * [4]       // -> 16
    [] * []         // -> 0
    [4, 4] * [4, 4] // NaN
    

    All looked good until we got to:

    '3' - 1  // -> 2
    '3' + 1  // -> '31'
    

    When we subtracted, the string and the number interacted as numbers. But during the addition, both acted as strings. Why? Well… as maddening as it might seem, JS is designed to things this way. Luckily, there’s a simple table that will help you understand how the language handles each case like this:

    Number  + Number  -> addition
    Boolean + Number  -> addition
    Boolean + Boolean -> addition
    Number  + String  -> concatenation
    String  + Boolean -> concatenation
    String  + String  -> concatenation
    

    What about the other examples? The ToPrimitive and ToString methods are being implicitly called for [] and {} before addition. Read more about evaluation process in the specification:

  • 12.8.3 The Addition Operator (+)
  • 7.1.1 ToPrimitive(input [,PreferredType])
  • 7.1.12 ToString(argument)
  • Notably, {} + [] here is the exception. The reason why it differs from [] + {} is that without parenthesis it is interpreted as a code block and then a unary +, converting [] into a number. It sees the following:

    {
      // a code block here
    }
    +[]; // -> 0
    

    To get the same output as [] + {}, we can wrap it in parenthesis.

    ({} + []); // -> [object Object]
    

    Conclusion

    Again, JavaScript is an amazing language — it just happens to be one that's got some tricks and weirdness. I hope this article brings you some clarity into some of these interesting quirks and that next time you encounter something like them you can more easily figure out what exactly is happening.

    And by the way, since what we went over above are only some of the situations under which JS can be very weird, I'm officially leaving the door open for a Strange Things in JavaScript Part 2 blog.

    Juan’s Live Code Stream writing can be sent to you as a free weekly newsletter. Sign up for updates on everything related to programming, AI, and computer science in general.

    Get offers from top tech companies

    Take our coding quiz

    Discussion

    Liked what you read? Here are some of our other popular posts…

    Triplebyte’s Way-Too-Long Technical Interview Prep Guide

    By Triplebyte on Apr 29, 2020

    A running collection of technical interview preparation resources that we've collected at Triplebyte.

    Read More

    How to Pass a Programming Interview

    By Ammon Bartram on Apr 29, 2020

    Being a good programmer has a surprisingly small role in passing programming interviews. To be a productive programmer, you need to be able to solve large, sprawling problems over weeks and months. Each question in an interview, in contrast, lasts less than one hour. To do well in an interview, then, you need to be able to solve small problems quickly, under duress, while explaining your thoughts clearly. This is a different skill. On top of this, interviewers are often poorly trained and inattentive (they would rather be programming), and ask questions far removed from actual work. They bring bias, pattern matching, and a lack of standardization.

    Read More

    How to Interview Engineers

    By Ammon Bartram on Jun 26, 2017

    We do a lot of interviewing at Triplebyte. Indeed, over the last 2 years, I've interviewed just over 900 engineers. Whether this was a good use of my time can be debated! (I sometimes wake up in a cold sweat and doubt it.) But regardless, our goal is to improve how engineers are hired. To that end, we run background-blind interviews, looking at coding skills, not credentials or resumes. After an engineer passes our process, they go straight to the final interview at companies we work with (including Apple, Facebook, Dropbox and Stripe). We interview engineers without knowing their backgrounds, and then get to see how they do across multiple top tech companies. This gives us, I think, some of the best available data on interviewing.

    Read More