JavaScript variables: questions that will make you go hmm...

At first I wanted to write about variables & scope in JavaScript, but since there are already great articles on this matter I decided to put the topic in another light by asking some different questions.

surprised kid Photo by Ben White on Unsplash

Agenda

😜 How many variables does it take to crash the browser?

Because every variable and function resides in the device's RAM memory, there's a limit to their number. This limit is volatile though, because 1 million characters occupy much less space than 1 million 10-digit numbers. So first let's agree on a baseline.

I think the timestamp is a great fit because it's easy to create - Date.now() - it's always different so possible optimization from the JS engine won't get in our way, and it's relatively big - 13 digits - thus we're not gonna need as many of them to fill up the RAM.

With this in mind I created the script below which adds tons of variables to an array. Then I started playing with the SIZE value until I hit the sweetspot of around 50 million and Chrome started crashing.

const SIZE = 50 * Math.pow(10, 6);
let dates = [];

for(let i = 0; i < SIZE; i++) {
    dates.push(Date.now())
};
Mistery solved?

Well...not so fast. It seems that this indeed is a limit, but a limit for the maximum size of an Object. We can push the total memory limit even further by increasing the number of arrays. My PC could handle up to 6 x 50 million arrays , moment when Chrome was using around 2 GB's of RAM and crashed.

screenshot of Chrome crashing Screenshot of Chrome

However, the PC was at about 60% out of the total 8GB's of RAM so I'm guessing there's a limit on how much memory Chrome allows, otherwise it should have kept going even further.

...

In terms of NodeJS, even though it uses the same V8 engine as Chrome, things are a whole different story. Node allows you to modify the memory limit through it's --max-old-space-size flag, so I was able to crank it up to 4 GB's and thus hold around 500 million timestamp variables in memory at the same time!

const v8 = require('v8');

gc();

const SIZE = 50 * Math.pow(10, 6);

let dates = [];
let dates2 = [];
let dates3 = [];
let dates4 = [];
let dates5 = [];
let dates6 = [];
let dates7 = [];
let dates8 = [];
let dates9 = [];
let dates10 = [];

for(let i = 0; i < SIZE; i++) {
    dates.push(Date.now())
    dates2.push(Date.now())
    dates3.push(Date.now())
    dates4.push(Date.now())
    dates5.push(Date.now())
    dates6.push(Date.now())
    dates7.push(Date.now())
    dates8.push(Date.now())
    dates9.push(Date.now())
    dates10.push(Date.now())
};

console.log(v8.getHeapStatistics());

The above code has 2 notable differences:

To execute the above script named memory.js I had to add two extra flags: the --max-old-space-size mentioned earlied as well as --expose-gc for access to the garbage collector.

$ node --expose-gc --max-old-space-size=4096 memory.js

And these are the heap statistics after allocating all 500 million variables. It seems they consume almost all the 4GB's of memory I've given to Node.

screenshot of Chrome crashing heap statistics

I'm pretty sure that I could have gotten even more variables, because the memory consumption was not at 100%, but my computer was sounding like it desperately wanted to take off so I decided to stop the experiments.

So, in conclusion:

300 million in Chrome
500+ million in NodeJS

on my 8GB's PC.

But, why should you care?

Well, in most cases you shouldn't. You can create complex apps without knowing how much memory can Chrome or NodeJS hold or even how to extend it. However, in some cases, when the Back-end needs to be extremely performant, it might make sense to keep part or all the database in memory. This way you save the expensive time-cost of reading from the HDD.

💭 What if variable declaration would allow whitespaces?

camelCase or snake_case? Or maybe...PascalCase? And of course UPPERCASE_SNAKE_CASE for constants right? The subject of which naming convention to follow is heavily debated online, a debate I don't want to enter right now.

Instead I want to explore one solution to this entire debacle, proposed by Douglas Crockford in his Frontend Masters tutorial where he said something along the lines of:

We should have whitespaces in the name!

At the time of first seeing this I didn't think much of the idea. I thought it was interested but quickly dismissed it without further investigation. But now the time has come so let's do a side-by-side comparison. First a piece of pretty usual-looking code, written in camelCase.

const USER_TYPES = {
    ADMIN: "admin",
    USER: "user"
};

let buttonEl = document.getElementById("signUp");
buttonEl.addEventListener("click", () => {
    buttonEl.classList.add("btn--loading");

    AuthService.login().then(userInfo => {
        switch (userInfo.type) {
            case USER_TYPES.ADMIN:
                // ...
                break;
            case USER_TYPES.USER:
                // ...
                break;
            break;
        } 
    })

});

Now let's see how the same code looks like in whitespace case.


const USER TYPES = {
    ADMIN: "admin",
    USER: "user"
};

let button el = document.get element by id("sign up");
button el.add event listener("click", () => {
    button el.class list.add("btn--loading");

    Auth Service.login().then(user info => {
        switch (user info.type) {
            case USER TYPES.ADMIN:
                // ...
                break;
            case USER TYPES.USER:
                // ...
                break;
            break;
        } 
    })

});

Yaycks! This looks pretty different to what I'm used to.... There's so much empty space that I have to work harder to figure out what's going on. Definitely not a good thing.

What if the editor would mark the full names to help us?
const USER TYPES = {
    ADMIN: "admin",
    USER: "user"
};

let button el = document.get element by id("sign up");
button el.add event listener("click", () => {
    button el.class list.add("btn--loading");

    Auth Service.login().then(user info => {
        switch (user info.type) {
            case USER TYPES.ADMIN:
                // ...
                break;
            case USER TYPES.USER:
                // ...
                break;
            break;
        } 
    })

});

Wow, this doesn't look too bad after all! I cannot say I'm convinced just by seeing this much code but I wouldn't hesitate to give it a try if JavaScript and VSCode would support this.

So then, why don't we have it as an option?

Well, in JavaScript we kinda do! ❤️ Although variables themselves cannot have white spaces, they are allowed inside property names:

user["first name"] = "Bob";
user["last name"] = "Smith";

Now, regarding actual whitespace variables, after a lot of online reading it seems that the issue is not technical but actually a choice of language designers. It might mean more work and multiple edge-cases but it would definitely be possible to have whitespace case in JavaScript.

But then again, do we really want to have another way of naming variables?

👨‍🔬 Can we replicate the behavior of const without const?

YES!*

But first, let's talk about the goal.

We want to somehow declare and assign a value to a new variable, for example the array [1, 2]. We then want to be allowed to change it's contents, for example .push(3) should work on it and append the number 3. However reassigning it should fail with the error: TypeError: Assignment to constant variable! just like const does.

It might seem impossible at first...

Variable declaration is a feature of the JS engine, right?

... but by using getters and setters we can at least simulate the behaviour for global variables. *

Getters and setters allow us to run some code every time an object's property is being read or set.

And, if we think of the window as that object - KABOOM! - we can simulate the behaviour of const for global variables.

function createConst(name, startValue) {
    Object.defineProperty(
        window,
        name, {
            get: function () {
                return startValue;
            },
            set: function () {
                throw new TypeError("Assignment to constant variable.");
            }
        });
}

The code above declares a new property on the window object, in other words a global variable. Every time we try to get it's value, the get function is being called which returns the startValue.

If we try to reassign it, the set function gets invoked which not only doesn't set the value but actually throws an assignement error! Here's the code in action:

mimicking the const declaration

So, there you have it! I hope you now look a little bit different at the plain-old variable :)

Cheers! 🥂

Portrait of Pava
hey there!

I am Pava, a front end developer, speaker and trainer located in Iasi, Romania. If you enjoyed this, maybe we can work together?