Skip to main content

Death by a thousand inconsistencies 💀

I think consistency is one of the most important design guidelines.

When things are consistent, we use our hard-earned knowledge about how things have worked in the past to understand how new things work. Consistency helps us avoid superfluous and distracting choices. You don’t need to think about whether X is different from Y for a good reason, or just because the developer was bored that day. You can focus your energy where it really matters.

I’ve been learning a lot about JavaScript and TypeScript lately. (It is an intentional choice, so this is not a rant by a disgruntled developer forced to use an unfamiliar language.) Doing so has given me ample opportunity to reflect on consistency.

Example: The JavaScript event loop #

Let’s talk about the event loop. A single thread runs non-blocking code off of a single queue. Any other work is put on the queue for later processing. No-one will interrupt your code during your execution. You don’t have to deal with concurrent data access. Good resource allocation. Such a nice and simple abstraction. Everybody’s happy!

Except it’s not true. (If it had been true, this would have been a very different blog post.) To begin with, there is not just one queue. In addition to the ordinary task queue, promises adds a microtasks queue which has higher priority. If you call requestAnimationFrame, the request is put on yet another queue. And if you work with Node.js it adds a nextTick task queue as well.

“Ok, so there might be multiple nested task queues, but at least you don’t have to think about concurrent access.” Well… except for when you resize the browser window or the user puts focus on another form input element. When the browser processes these events (and a few more), it just hijacks your stack as it is and appends a new frame to execute the new event handler. When the original task gets the stack back, it has no idea it was ever interrupted and no idea that the state may have changed.

The value of consistency #

This is just one example. Unfortunately it is easy to find more.1 2 3 4 5 6 7 8 9 10 11 12 13 Together they turn what could have a been a nice, simple and consistent language into a quagmire of surprises. (To be fair, it should be noted that the language is moving in the right direction. And thanks to strict mode, linters, and TypeScript, you can get work done with most of your sanity intact.)

However, the real purpose of this post is not to point out flaws in JavaScript. It is to highlight the importance of consistency. It is easy to find faults with JavaScript just because it is so inconsistent. Together these inconsistencies make learning JavaScript harder, and the experience of using it worse. JavaScript also highlights how much effort goes into correcting or mitigating those inconsistencies once they’re released.

So if you want what you build to be easy to understand and use – be consistent! Remember that whenever you make an exception from what was previously a rule, you make the whole thing harder to learn. You remove a little bit of the trustworthiness of the system. You make it worse.

Be consistent!

  1. The class syntax is just syntactic sugar on top of the prototype-based inheritance mechanism. Except it also adds support for private fields which are not available without class syntax. ↩︎

  2. JavaScript’s type coercion behavior is famously weird: [] == [] is false but [] == ![] is true. ↩︎

  3. When using var, you can reference variables before they have been defined, but not when using let or const↩︎

  4. Objects of RegExp are stateless, unless the global or sticky flag is set. Then they store match iteration state. ↩︎

  5. Calling Array(10) creates an array of length 10, while Array(10, 20) creates an array of length two, with elements 10 and 20↩︎

  6. The Array type uses length to denote the number of elements, while Set uses size↩︎

  7. Higher-order collection functions mapand filter are present in an array’s prototype. The groupBy function is defined on Object↩︎

  8. While null is a keyword, undefined is a property on the global object. (And it used to be assignable!) ↩︎

  9. The obj.__proto__ property as a way to interact with an object’s prototype is considered non-standard and discouraged. The { __proto__: p, ... } syntax to set an object’s prototype is standard. ↩︎

  10. Calling new Date() gives you a Date, but calling Date() gives you a string. ↩︎

  11. The value undefined is assigned to (formal) arguments when no value is provided, but Number() is 0 while Number(undefined) is NaN↩︎

  12. The expression [,0] produces an array of length two, but [0,] produces an array of length one. ↩︎

  13. I could keep on going all day… 😉 ↩︎