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.
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
readonlyto 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