April 2, 2021
Why Chrome’s Developer Console Sometimes Lies
Don’t let lazy evaluation trip up your JavaScript logging code

By Matthew MacDonald
2 min read
Let's start with something straightforward. Here's a JavaScript snippet that creates a small array of numbers, then changes it. The array is logged to the console, both before and after the change:
const numbers = [2, 3, 4, 5];
console.log(numbers);
// Square the numbers
for (let i = 0; i<numbers.length; i++) {
numbers[i] = numbers[i]**2;
}
console.log(numbers);const numbers = [2, 3, 4, 5];
console.log(numbers);
// Square the numbers
for (let i = 0; i<numbers.length; i++) {
numbers[i] = numbers[i]**2;
}
console.log(numbers);A more careful approach would use Array.map() to process the array instead of a for...of loop. (That way your changes would be applied, non-destructively, to a new array.) But there's a reason I've chosen this approach. It demonstrates the first example of a certain pesky quirk in the developer console.
To see the problem in action, open this page in a Chromium-powered browser (like Chrome or Edge), then open the developer console, and then expand the array listings in the log. You'll see two arrays, but they'll both be instances of the changed array:
Why does this happen? If the console window is closed when the code runs, and you log an object, a lazy-evaluation pattern kicks in. Your console.log() command actually logs a reference to the array. In the interest of saving memory and optimizing performance, Chrome doesn't bother to grab the information out of the array until you expand it in the console, which is after it's been changed to its final form.
There are plenty of ways to sidestep this problem. Open the console before you load the page. Or explicitly convert the array to a string when you log it, because strings won't use lazy evaluation:
console.log(numbers.toString());console.log(numbers.toString());And that should be the end of the problem. Except it's not.
Things get worse if you have an array that holds objects. Consider the next example, which creates an array with two literal objects, each representing a person. It logs the array before and after changing one of the objects:
const objects = [ {name: 'Sadie', age: 12},
{name: 'Patrick', age: 18}];
console.log(objects);
objects[0].age = 14;
console.log(objects);const objects = [ {name: 'Sadie', age: 12},
{name: 'Patrick', age: 18}];
console.log(objects);
objects[0].age = 14;
console.log(objects);This time, it doesn't matter if the console window is open when the code runs. Even if the array is not lazily evaluated, the objects inside it are. Expand them, and you'll get the same sort of result you saw before — two log entries with the same changed object. Try it out.
How do you resolve this problem? You can convert the array to a string, but not with a simple call to toString(). Instead, you'll need to write an algorithm that does it yourself, or use a library, or use JSON. But of course, the real problem is that this error could crop up in nested object hierarchy when you aren't expecting it, and you'll unwittingly accept the information in the developer console when it's inaccurate.
The best protection is to be aware of the possibility of lazy evaluation. Another good practice is to write targeted logging code to output specific properties that have primitive data types. Don't log whole objects and rely on the expandable view in the console, unless it's completely necessary.
For some modern replacements to old JavaScript practices, check out Do This Not That, JavaScript Edition. And for a once-a-month email with our best tech stories, subscribe to the Young Coder newsletter.