The true and false value

August 26th, 2019

Recently my company deployed some application changes that exposed a flaw in a long-running part of the application that seemingly should not have been impacted by the changes that were made. Our application facilitates digital private placements and part of that process includes allowing investors to download a series of documents. Those documents weren't showing up in the UI though they were present in the database and in the response from the API. The problem was clearly in the JavaScript portion of our application and the entire team was anxious to get this fixed. After many hours of debugging and looking at data we finally discovered that a computation was being seen as both truthy and falsey at the same time.

The Problem Computation

For the particular section of the app that was causing the problem we were gathering a collection of objects from the API that represent documents to download. It can be conceptualized a little something like this:

documents = [
  {id: 1, type: "financial"},
  {id: 2, type: "promotional"},
  {id: 3, type: "regulatory"},

Each document type could be listed multiple times, and we group documents by type into a specific section of the page. To get the documents that belong in each section we have a JS Controller that filters the 1 general collection of documents from the API into many specific collections of documents with specific types. You can think of it something like this:

financialDocuments = filterDocumentsByType(documents, 'financial');
promotionalDocuments = filterDocumentsByType(documents, 'promotional');
regulatoryDocuments = filterDocumentsByType(documents, 'regulatory');

The filterDocumentsByType method will always return an array; either one with a set of document objects in it or an empty array.

Now, the computation that was causing us problems:

hasDocuments() {
  promotionalDocuments || financialDocuments || regulatoryDocuments

This truthy check was ultimately what determined whether or not documents would appear on the page and the documents weren't showing up.

The Diagnosis

It is important to remember that in JavaScript the || operator is not a boolean operator and will instead start executing the left-most expression and return the first truthy vaue hit or the last value in the chain. It is also important to remember that in JS an empty array is considered a truthy value. You can verify this for yourself by opening up a console and running || statements such as [] || null. Since filterDocumentsByType always returns an array we were always returning a presumably truthy value and we were only checking the first set of documents as the first expression returns a truthy value and short-circuits the rest of the checks.

The particular private placement we were testing had no promotional documents, a highly uncommon occurrence, that led to an empty array being returned from hasDocuments. While JavaScript sees an empty array as a truthy value the templating engine we were using did not. Our controller firmly believed that there were documents and it tried to tell the template but the difference in how each system perceived the type of an empty array caused a mission-critical bug to appear in our application. Fortunately it was a fairly simple fix and we were able to get our application online.

The Takeaways

I've been programming either professionally or as a hobby for 10 years and I've never experienced a situation quite like this one before and there were a lot of takeaways from the experience. Likely the biggest takeaway though is that... types are important! Even in dynamic languages that allow you to take great freedoms with how you use types. The differences in how each layer in your application interprets types is important to be aware of. When you're writing code that passes data between those boundaries it is even more important.

Comments for articles on this blog are handled through issues on a GitHub repository. To make comments you will be required to have a GitHub account.

The comments for this article can be found at this issue.