DEV Community

Cover image for 5 JavaScript Habits That Look Clean But Quietly Wreck Your Code
Exact Solution
Exact Solution

Posted on

5 JavaScript Habits That Look Clean But Quietly Wreck Your Code

You have been writing JavaScript for a while now. Your code is readable. Your colleagues do not complain. Your pull requests get approved without much debate.

And yet something keeps going wrong in production that you cannot quite explain.

The problem might not be what you are doing wrong. It might be what you are doing right — in the wrong context.

Here are five habits that look perfectly reasonable and are actively making your codebase worse.

Using async/await everywhere without thinking about parallelism

This one is everywhere. And honestly it looks so clean that most people never question it.

Looks fine right? Three clean awaits. Very readable. Very modern.
Except you just made three sequential network calls that have absolutely no dependency on each other. You are waiting for the user to load before even starting to fetch posts. Then waiting for posts before even thinking about comments.

On a fast connection with a healthy server this adds maybe 100ms. On a slow mobile connection it adds 600ms. And users on slow connections are already struggling.

The fix is obvious once you see it:

Same result. All three requests fire simultaneously. I have seen this single change cut perceived load time in half on real production apps.
The irony is that sequential awaits feel more controlled. More readable. More intentional. Which is exactly why people keep writing them without thinking about what is actually happening under the hood.

2. Storing everything in useState when it is not actually state

React developers do this constantly. I did it constantly for longer than I would like to admit.

Something needs to persist across renders so you reach for useState. Makes sense. Except not everything that persists is actually state.

Derived values, computed results, values that come directly from props — none of these need to be in state. But they end up there anyway because useState is the first tool we reach for.

Now you need to keep this in sync with firstName and lastName manually. And you will forget. And then you will have a bug that only appears when the user updates their first name without their last name changing first. And you will spend 45 minutes debugging what should not exist at all.

Done. No state. No sync. No bug.
The rule I try to follow now — if a value can be computed from existing state or props, it should not be state. Every piece of unnecessary state is a future synchronization bug waiting to happen.

3. Writing utility functions inside components

This happens when you are moving fast and it feels fine in the moment.

Clean looking component. Self contained. Everything in one place.
Except formatDate gets recreated on every single render. Every time the parent re-renders, every time any state changes, every time anything touches this component — a brand new function is created and immediately thrown away.

On one component this is basically nothing. On a list rendering 200 UserCards with multiple utility functions each, you are creating and garbage collecting hundreds of functions per render cycle for no reason.
Move utility functions outside the component. If they need to be reused elsewhere, they were always going to end up there anyway. If they do not, at least they are created once.

The muscle memory of writing everything inside the component is hard to break. But once you start noticing the pattern it is impossible to stop seeing it.

4. Not cleaning up event listeners and subscriptions

This is the one that creates ghost bugs. The kind that appears three months after you shipped a feature and only happens on specific navigation patterns that your tests never covered.

Every time this component mounts it adds a resize listener. Every time it unmounts — which in a typical SPA happens constantly — the listener stays. Navigate to this page ten times and you have ten resize listeners all firing simultaneously calling handleResize on a component that no longer exists.

The cleanup function is not optional. It is the entire point.
I once inherited a codebase where certain pages became noticeably slower the longer you used the app. The cause was an analytics tracking listener being added on every route change and never removed. After about 20 minutes of normal app usage you had 80 to 100 duplicate listeners all firing on every user interaction.

Nobody had noticed because nobody tested long sessions. They opened the app, clicked around for 2 minutes, and called it done.

5. Using console.log for debugging and shipping it to production

This sounds too obvious to include. It is not.

console.log in production is not just a minor embarrassment. In tight loops, frequent event handlers, or anywhere with high call frequency it is a real performance drain. The browser has to format the output, write to the console buffer, and do this every single time even when the devtools are closed.

But the more common version of this problem is not the obvious console.log("user data:", user) sitting in your render function. It is the logging that was added during a debugging session six months ago, wrapped in some condition that seemed safe, and has been silently firing in production ever since.

If you are going to log, at least make it conditional. Better yet, use a proper logging library that handles environment awareness and gives you log levels you can actually control.
The real fix is a pre-commit hook or ESLint rule that flags console.log statements before they ever reach main. Set it up once and never think about it again.

Final thought

None of these patterns are obviously wrong when you first write them. That is the whole point.

Sequential awaits feel controlled. State feels like the right place to store things. Utility functions inside components feel self contained.

Missing cleanup feels like an edge case. Console logs feel temporary.

They all made sense at the time. They all compound over time.

The best code reviews are the ones where someone asks "why is this here" about something that felt completely natural when it was written. Build that habit yourself and you will catch most of these before they become production problems.

Exact Solution is a certified refurbished electronics marketplace shipping across Europe. We stock the best refurbished laptops from Apple, Dell, HP, and Lenovo — all fully tested and ready to ship. We keep our code as clean as our laptops — mostly

Top comments (0)