Discussion about this post

User's avatar
BMoncef's avatar

Thank you for the detailed write-up. The lessons and questions raised are indeed very important to have in mind when evaluating tech stacks and design choices.

I think in some places there’s a conflation of the underlying execution model and the programming model, and the post would benefit from clarifying this: Go coroutines, and « native concurrency » in general, are presented as the key ingredient that enabled this performance unlock. While coroutines probably played a significant role in the choice of the language, the underlying multithreaded nature of their scheduling in Go and the parallelism that ensues is the actual differentiating factor. In other words, if Go, or an other language, offered coroutines as a programming model but was still single-threaded, multiplexing the routines on that single thread, it would definitely be « natively concurrent », but would exhibit the same issues as javascript for writing compilers or cpu-bound tasks more generally. It would be nicer to program in, but not faster.

Expand full comment
Luke's avatar

Thanks a lot for composing such a detailed post that addresses an issue that the bulk of the tech industry glosses over.

The "Node.js is slow, Golang is fast" talk stems from the problem of direct comparison of a runtime (Node.js) and a programming language (Go) that's so prevalent in most development circles.

Quite a number of pple think of Node.js as a programming language on its own. This is a problem brought about by the abstraction prevalent in computing where arcane things are hidden away from the programmer. Had there been enough readily available resources on how runtimes work, then I guess we wouldn't be facing this problem.

The JavaScript vs C/C++ boundary in Node.js is also quite obfuscated to most people since the Dev is almost always interacting with JavaScript wrappers around C++ functions unless they're writing Native API modules.

Can the latest improvements in Node.js to improve its handling of CPU-bound tasks such as cluster, child_process, and especially worker_threads offer similar capabilities to the ones gained from moving to Go? I don't know how worker_threads work under the hood but I guess it's implemented using a user-space threading library such as pthreads. If that's the case, can the underlying threading mechanism be improved like how Java released their own Virtual Threads in Java-21 that are lightweight just like Goroutines are, while still maintaining the traditional heavy JVM platform threads?

Can the various compilation stages you've outlined be triggered as events then handled by worker_threads considering Node.js is event-driven?

I know achieving these recommendations is not easy, considering at the time of it's release (2009), Node.js was built with an architecture that was meant to solve a particular problem (Handling I/O tasks) really well, and you can't move out of that architectural pattern without destroying what Node.js actually is.

Expand full comment
2 more comments...

No posts