LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #6: Why you shouldn't fully trust console.log – The silent deceiver
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #6: Why you shouldn't fully trust console.log – The silent deceiver

Don't let the colorful logs in Chrome DevTools fool you. The story of a time I almost 'lost my mind' because I believed what I saw instead of what was actually happening.

TP
Truong PhamSoftware Engineer
PublishedMarch 25, 2024

There's a common joke in the dev world: "If you don't use TDD, use CLDD (Console Log Driven Development)." I was the same; for years, console.log was my best friend, the "light" that helped me see into the internals of complex asynchronous code.

But one afternoon, that friend betrayed me bitterly. I was debugging a bug related to updating User Profile info. I logged the user object right before sending the API call. The console displayed: role: "admin". But the Backend reported: role: "user".

I spent 4 hours arguing with the Backend, making them check logs, making them review the Database. Until I realized I was looking at an "illusion" in DevTools.

1. The Brutal Truth: Console is not a "Snapshot"

Most of us think console.log(obj) will print the state of obj at that exact moment. But in reality, in Chrome DevTools, when you log an Object or Array, it only saves a reference.

When you click the small arrow to expand the object and see details, that's when the browser reads the data from memory. If the object is changed by another piece of code between the time you console.log and the time you click, you will see the modified value, not the original value at the time of logging.

2. The Problem: When Mutation "steps ahead"

The project I was working on was a CMS. When a user hits Save, I perform a series of data transformation steps before sending it off.

// The code that cost me 4 hours of my life
const onSubmit = (formData) => {
  console.log('Sending data:', formData); // I see: { id: 1, status: 'draft' }
  
  const processedData = prepareData(formData); 
  // Inside prepareData, someone wrote: formData.status = 'published';
  
  api.save(processedData);
};

When I opened the Console, I saw status: 'published'. I wondered: "Wait, I just logged before prepareData, didn't I?". I mistakenly thought prepareData ran first, or there was a race condition. In fact, it was simple: by the time I clicked to view formData in the console, the prepareData function had already finished and mutated the value of formData. console.log had "lied" to me.

3. The Process: Looking for Alibi Evidence

After being "scolded" by the Backend for blaming them wrongly, I started to calm down. I used an old but always effective technique to create a real Deep Clone (Snapshot):

console.log('Real Snapshot:', JSON.parse(JSON.stringify(formData)));

And boom! The truth was out. In this "Snapshot" version, status was still 'draft'. So the culprit was the prepareData function. It had executed a "Side Effect" on the input instead of returning a new clone. A basic mutation error that anyone can make when working with Objects in JavaScript.

4. Trade-offs: Is JSON.stringify bad?

Abusing JSON.parse(JSON.stringify()) for logging has a major performance downside if the object is too large. It also loses properties that cannot be serialized like Functions, Maps, Sets, or Date objects.

Moreover, if you log too many of these Snapshots in a loop, you're consuming a huge amount of CPU resources just for debugging.

If I were to start over, I would use the Debugger (Breakpoint). The Debugger stops the entire execution flow of JavaScript at that exact line, allowing you to examine every value without worrying about changes from other code. That's the pro way, instead of "scattering" logs everywhere.

5. Junior vs. Senior: Trust and Doubt

A Junior developer often takes Console Log as the truth. "If the console shows this, the code must be this." They rarely doubt the tools they use.

A Senior developer always maintains a "healthy skepticism." They understand that all debugging tools have certain limitations. When they see illogical data, instead of adding more logs, they use a Breakpoint or Object.freeze() to protect the data from unauthorized mutation.

Sustainable solutions:

  • Always write code following the Immutable principle. Don't mutate inputs.
  • Use TypeScript and readonly to have the compiler report errors when you try to change data.
  • Learn to use the Sources Tab in Chrome to set Breakpoints. It's 100 times more powerful than console.log.

6. Lessons Learned

console.log isn't wrong, but our trust in it is.

  • Never trust the values of Objects/Arrays in the console if you haven't snapshotted them (using JSON or cloning).
  • Mutation is the source of all evil in state management.
  • Learn to use the Debugger as soon as possible. It saves you hundreds of hours of "arguments" with colleagues.

Final advice: Next time you see weird data in the Console, don't rush to call the Backend. Try JSON.stringify or set a Breakpoint. Your code might be "acting out" behind your back.


Don't let the light of console.log blind you to the truth of memory.

Series • Part 6 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #7: The 'Double' Nightmare – When React StrictMode tests your patience
Blog #5: 200 OK – The system's sweet little lies
01Blog #1: When 'It works on my machine' is the sweetest lie02Blog #2: The 60 FPS Illusion – When your MacBook Pro 'deceives' your senses03Blog #3: Infinite Loops – When useEffect turns the browser into a 'furnace'04Blog #4: Hydration Error – When SSR's perfection 'slaps' me in the face at midnight05Blog #5: 200 OK – The system's sweet little lies06Blog #6: Why you shouldn't fully trust console.log – The silent deceiverReading07Blog #7: The 'Double' Nightmare – When React StrictMode tests your patience08Blog #8: Memory Trash – The price of forgetting to 'clean up' your Event Listeners09Blog #9: Safari – The new 'Internet Explorer' of the modern era?10Blog #10: Specificity War – When !important marks the start of a civil war11Blog #11: Don't wait until 'heavy web' to lose weight for your Bundle
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles