10 Essential JavaScript Best Practices for Clean Code

text
<— SYSTEM FAILURE ANALYSIS REPORT: PROJECT ICARUS —>
<— TIMESTAMP: 2024-05-14 03:14:22 UTC —>
<— STATUS: TERMINATED (SIGKILL) —>

<— TERMINAL LOG: STACK_TRACE_DUMP —>
FATAL ERROR: Reached heap limit Allocation failed – JavaScript heap out of memory
1: 0x101302304 node::Abort() [/usr/local/bin/node]
2: 0x10130248c node::OnFatalError(char const, char const) [/usr/local/bin/node]
3: 0x1014a46d0 v8::Utils::ReportOOMFailure(v8::internal::Isolate, char const, bool) [/usr/local/bin/node]
4: 0x1014a4664 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate, char const, bool) [/usr/local/bin/node]
5: 0x101642898 v8::internal::Heap::FatalProcessOutOfMemory(char const) [/usr/local/bin/node]
6: 0x1016411a0 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/usr/local/bin/node]
7: 0x10163d81c v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/local/bin/node]
8: 0x10163af90 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/local/bin/node]
9: 0x10162e734 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node]
10: 0x10162f010 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node]
11: 0x101614058 v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/local/bin/node]
12: 0x1019df270 v8::internal::Runtime_AllocateInYoungGeneration(int, v8::internal::Object
, v8::internal::Isolate) [/usr/local/bin/node]
13: 0x101d8f35c Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit [/usr/local/bin/node]


ERR_IPC_CHANNEL_CLOSED: [Icarus-Main-Process] The worker process died unexpectedly.
Exit code: 134 (OOM)
Uptime: 42 seconds.
<— END LOG —>

I told them. I told them three months ago that you can’t just keep shimming legacy CommonJS modules into a modern ESM environment and hope the V8 engine figures it out. But no. The “Lead Visionary”—a kid who learned React from a 10-minute TikTok tutorial—decided that “types are just suggestions” and that “the Event Loop is basically magic.”

Well, the magic just ran out of mana. Project Icarus is dead. 1.2 million lines of unoptimized, un-tree-shakable garbage. I’m sitting here at 3:00 AM with a lukewarm espresso and a terminal full of heap dumps, writing this autopsy because someone has to explain why $4 million in VC funding just evaporated into a JavaScript heap out of memory error.

Incident Report 01: The “Any” Type Contagion and TypeScript 5.4 Sabotage

We started with TypeScript 5.4. A solid foundation. But the “rockstars” hated the friction of actually defining interfaces. They saw strict: true in the tsconfig.json as a personal insult. Slowly, the any types started creeping in. It started in the API layer—”just for speed,” they said. Then it hit the state management.

The javascript best practice for modern TypeScript is clear: use unknown for unpredictable data, leverage type guards, and maintain strict null checks. Instead, Icarus was built on a foundation of as any. By the time we hit the production build, the compiler wasn’t checking anything. It was just a glorified transpiler for broken JavaScript.

The “ugly reality” was a UserContext that looked like this:

// The "Rockstar" way
const UserContext = createContext<any>(null); 

// The actual data structure at runtime (Node v20.11.0)
{
  id: "123",
  metadata: {
    lastLogin: undefined, // Should have been a Date
    permissions: "admin" // Should have been an Array
  }
}

Because they bypassed the type system, the runtime logic was littered with if (user && user.metadata && user.metadata.permissions). One missing check in a deeply nested component, and the whole React tree would unmount. A “seamless” experience? Hardly. It was a minefield. We spent 40% of our sprint cycles fixing “Cannot read property ‘x’ of undefined” errors that a basic linter config would have caught in 2014.

Incident Report 02: The Event Loop Blockage and Synchronous Sins

Node.js is single-threaded. This is the first thing you learn. This is the last thing they remembered. In the heart of the Icarus-Server-Core, I found a synchronous JSON parser processing 50MB telemetry blobs on the main thread.

// Found in /src/middleware/telemetry.js
const processData = (raw) => {
  const data = JSON.parse(fs.readFileSync(raw)); // BLOCKING THE LOOP
  return transform(data); 
};

When the traffic spiked, the Event Loop latency hit 800ms. In Node v20.11.0, we have worker threads. We have fs.promises. We have non-blocking I/O. But the team ignored javascript best standards for asynchronous execution because readFileSync was “easier to reason about.”

While the main thread was busy choking on a 50MB string, incoming HTTP requests were piling up in the kernel’s socket buffer. The health checks failed. Kubernetes, in its infinite, automated wisdom, decided the pod was dead and killed it. This triggered a cascading failure. New pods would spin up, immediately ingest the same massive telemetry file, block their own event loops, and die. It wasn’t a system; it was a suicide pact.

Incident Report 03: The Dependency Debt Trap

I ran an npm audit on the final build. I should have worn a hazmat suit.

$ npm audit
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Final Report: Project Icarus
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
found 482 vulnerabilities (112 moderate, 284 high, 86 critical)
run `npm audit fix` to fix them, or `npm audit` for details

Dependencies: 2,412
Node Version: v20.11.0
Total Size: 1.4GB (node_modules)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Two thousand dependencies for a dashboard. Let that sink in. We had three different versions of lodash. We had moment.js (deprecated for years) fighting with date-fns and luxon. We had a “rockstar” who added a 400KB library just to left-pad a string because he didn’t know String.prototype.padStart() existed in ECMAScript 2017.

The javascript best approach is to keep the dependency tree lean. Use npm-check-updates. Audit your licenses. Tree-shake your builds. But Icarus was a “tapestry” (to use a word I hate) of technical debt. We were shipping 4MB of JavaScript to the client just to render a login page. The browser’s main thread would lock up for 3 seconds just parsing the dead code we weren’t even using.

Incident Report 04: Memory Leaks and the WeakMap Mirage

The OOM (Out of Memory) crash wasn’t an accident. It was a slow-motion car crash caused by a fundamental misunderstanding of memory management. The team tried to implement a custom caching layer using a standard Map.

// The Memory Leak
const cache = new Map();

export const getCachedUser = (id) => {
  if (cache.has(id)) return cache.get(id);
  const user = fetchUser(id);
  cache.set(id, user); // Objects never garbage collected
  return user;
};

In a long-running Node.js process, this is a death sentence. The Map holds a strong reference to every user object ever fetched. Even after the user logged out, even after the session expired, the object stayed in the heap.

The javascript best practice here is to use a WeakMap when the keys are objects, or at least implement a TTL (Time To Live) or an LRU (Least Recently Used) eviction policy. But they didn’t. They thought V8’s Garbage Collector was a magical janitor that would clean up their “clever” hacks. By the time I took a heap snapshot, the Map was holding 1.2GB of stale JSON data. The GC was spending 90% of its time trying to find memory to reclaim, eventually giving up and throwing the FATAL ERROR you saw at the top of this report.

Incident Report 05: CJS and ESM Interop Hell

We are living in 2024. Node v20.11.0 has excellent ESM support. Yet, Project Icarus was a Frankenstein’s monster of require() and import. We had ts-node shimming things at runtime, webpack trying to bundle the backend (why?), and a build pipeline that took 14 minutes to finish.

The “rockstars” didn’t understand the difference between a default export and a named export. They would try to import a CommonJS module that didn’t have a __esModule flag, leading to the infamous [object Module] errors. Instead of fixing the root cause—standardizing on ESM—they added more shims. More babel-plugin-transform-modules-commonjs. More complexity.

$ node --trace-warnings dist/index.js
(node:45210) [DEP0147] DeprecationWarning: CJS loader-only hook "resolve" is deprecated.
(node:45210) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/app/dist/index.js:1
import { Server } from './server.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module

The build failed in production because the CI environment had a slightly different version of npm than the local dev machines. Because they didn’t respect the javascript best practice of locking down the environment and using a consistent module system, the “it works on my machine” excuse became the project’s unofficial motto.

Incident Report 06: Hydration Mismatch and the React Monolith

On the frontend, things were even worse. They insisted on using Server-Side Rendering (SSR) for a dashboard that was 100% private data. Why? Because they heard SSR was “vibrant” for SEO. SEO for a private, authenticated admin panel. Think about that.

The result was a constant stream of hydration mismatches. The server would render one state, the client would calculate another (usually due to a timezone mismatch or a missing useEffect dependency), and React would throw a fit.

Warning: Expected server HTML to contain a matching <div> in <div>.
  at div
  at Dashboard (https://icarus.internal/static/js/bundle.js:4521)

Instead of fixing the state synchronization, they used the “suppressHydrationWarning” prop everywhere. A “comprehensive” solution? No. It was a band-aid on a bullet wound. The client-side JS would then proceed to re-render the entire DOM tree, defeating the entire purpose of SSR and making the site feel like it was running on a 56k modem. They ignored the javascript best practice of keeping the server and client state in sync, opting instead for “clever” hacks that bypassed React’s safety checks.

The Graveyard Shift

I’m looking at the git history now. The last commit before the final crash was titled: “Fixed the bug, don’t ask how lol.” It was a 4,000-line diff that touched 50 files. It removed the last remaining linter rules. It added three more any types. It was the final nail in the coffin.

Project Icarus didn’t fail because the technology was bad. Node.js is fine. React is fine. TypeScript is a godsend. It failed because the team treated “best practices” as optional suggestions for people who aren’t “rockstars.” They wanted to “elevate” the code without understanding the basement it was built on.

They forgot that JavaScript is a language of sharp edges. If you don’t respect the Event Loop, it will freeze. If you don’t respect memory, it will leak. If you don’t respect the type system, it will betray you.

The servers are dark now. The Slack channel is silent, except for the automated alerts still firing from a dead monitoring service. The “Lead Visionary” has already updated his LinkedIn to “AI Architect.” He’s probably out there right now, “embarking” on a new “journey” to ruin another codebase with “seamless” abstractions.

Me? I’m going to finish this coffee, delete the node_modules folder one last time, and go to sleep. There’s no “unlocking” the potential of this project. There’s only the final bill, and it’s been paid in full by the engineers who have to clean up the mess.

<— REPORT ENDS —>
<— SYSTEM SHUTDOWN —>
<— GOODBYE —>

Related Articles

Explore more insights and best practices:

Leave a Comment