Becoming a better front-end developer using fundamentals instead of heuristics

In my experience, developers that are self-taught or come from non-technical backgrounds tend to rely on heuristics to solve problems instead of fundamentals.

A heuristic can be thought of as a "pattern" or "rule of thumb" learned from experience. It may not be perfect or technically rigorous, but it's often "good enough" and tends not to require deep thought. Heuristics include things like:

  • "You should use $(document).ready(function() {}) to initialize code on a jQuery site."
  • "var self = this is necessary to make method calls work inside a callback."
  • "Arrow functions don't have return statements."

In contrast, a fundamental is a basic principle that can be used to construct solutions to other problems. It is always "correct", and is often a definition for how something works. Fundamentals include things like:

Notice how I quoted the heuristics examples but not the fundamentals examples. I did this to highlight the "rule of thumb" nature of heuristics in comparison with the technical rigor of fundamentals. None of the example heuristics are true in all cases; they just happen to be true in enough cases that developers can often apply them and produce working code without understanding why it works.

The case for a fundamentals-based approach

I've found that developers that come from non-technical backgrounds often aren't accustomed to solving problems using fundamentals. This is frequently because they didn't have the opportunity to learn the fundamentals at the start of their career, and since the heuristics approach was "good enough," they stuck with it.

Although it can be difficult at first, it's well worth the effort to learn to use fundamentals. Why? It will allow you to be confident that your solution works and derive answers to new problems without needing to see how someone else has solved it before. Relying on heuristics may be easier and quicker in the short term, but it often leads to imperfect solutions—if it leads to a solution at all.

Also, one of the big risks of relying on heuristics is that you never learn how to actually solve problems. You may be able to "make things work" often enough, but you'll eventually reach a point where you can't solve problems you haven't seen before. Heuristics are what "copy-paste programmers" rely on to do their jobs.

Heuristics vs. fundamentals as a gauge of developer skill

When I interview front-end developers for our team, I give them a programming challenge and invite them to use any online resource they want, including Google and Stack Overflow. This allows me to easily determine if a candidate is a "heuristics developer" or a "fundamentals developer".

Without fail, the heuristics developers copy and paste code from seemingly relevant examples on Stack Overflow. It's not until the code fails to work as expected that they change it to suit their needs. Often, they can't get it to work at all.

In contrast, the fundamentals developers tend to look up API documentation. In the API docs, they examine how many and what kind of parameters a particular function takes, or the specific syntax for the long form of a CSS property they want to use.

In the first 5 minutes of the interview, I can tell what kind of developer the candidate is.

Case example

Consider a developer named Bill. Bill did some tutorials and JavaScript challenges and made websites in his spare time but otherwise never "really learned" JavaScript.

One day Bill encounters an object like the following:

const usersById = {
    "5": { "id": "5", "name": "Adam", "registered": true },
    "27": { "id": "27", "name": "Bobby", "registered": true },
    "32": { "id": "32", "name": "Clarence", "registered": false },
    "39": { "id": "39", "name": "Danielle", "registered": true },
    "42": { "id": "42", "name": "Ekaterina": "registered": false }
}

This object could represent something like a mapping of users and whether they've registered for a particular event.

Now let's say Bill needs to obtain a list containing only the users who are registered. Translation: He needs to filter it. He has seen code before where .filter() was used to filter a list. So he tries something like the following:

const attendees = usersById.filter(user => user.registered);

and then in the console, he sees this:

TypeError: usersById.filter is not a function

"Well that doesn't make sense," thinks Bill. He has seen other code where .filter() worked to filter a variable before.

The problem is that Bill relied on a heuristic. What he doesn't understand is that filter is a method defined on arrays, whereas the variable usersById is a plain object and does not have a filter method.

Confused, he does a search for "javascript filter". Bill finds many mentions of arrays, so he realizes that usersById must be converted into an array. Bill then does another search for "javascript turn object into array" and finds some Stack Overflow examples using Object.keys(). Then, he tries the following:

const attendees = Object.keys(usersById).filter(user => user.registered);

This time it doesn't produce an error, but he's surprised to see that attendees is empty.

The issue is that Object.keys() returns the keys of an object, but not its values. In fact, the variable name user is misleading because isn't really a user object at all, but an ID, which is a string. Since registered is not a defined attribute on strings, the filter evaluates every entry as falsy, so the resulting array is empty.

Bill then looks at the Stack Overflow answers a little more closely and makes the following change:

const attendees = Object.keys(usersById).filter(id => usersById[id].registered);

The result is better this time: it contains ["5", "27", "39"]. However, this isn't what Bill wanted. These are the IDs of the attendees, whereas he wanted the attendee objects.

Frustrated, Bill searches for "javascript filter object" to figure out how to filter the attendees, and then looks through Stack Overflow results and finds this answer, which has the following code:

Object.filter = (obj, predicate) => 
   Object.keys(obj)
         .filter( key => predicate(obj[key]) )
         .reduce( (res, key) => (res[key] = obj[key], res), {} );

So Bill copies it and tries the following...

const attendees = Object.filter(usersById, user => user.registered);

...and it works, although he doesn't quite understand why. He has no clue what reduce does or why it was used. More, Bill doesn't realize that he just modified the global Object object to have a new non-standard method defined.

But, as far as Bill is concerned, it works! (And, to him, that's all that matters for now.)

Let's review where Bill went wrong

Bill tried to use heuristics for what seemed like the way to solve this problem and ran into these issues:

  1. Bill got a TypeError when trying to use .filter() on a variable. He didn't realize that filter isn't defined on plain objects.
  2. He used Object.keys() to "turn an object into an array", but that wasn't helpful on its own. He really needed to create an array of the object's values.
  3. Even when he obtained the values and used them as the filter predicate, he still only got IDs rather than the user objects associated with those IDs. This happened because the array being filtered contained the IDs, not the user objects.
  4. Bill eventually gave up on this approach and found a working solution online. However, he didn't—and doesn't—understand how it works and isn't going to take the time to learn how it works because he has other things to do.

This is a contrived example, but I've seen developers approach problems in similar ways many times. To solve problems effectively, we need to move beyond heuristics and learn to use fundamentals.

Now, for the fundamentals approach

Had Bill taken fundamentals-based workflow, his path might have look like the following:

  1. Identify the given input and define the desired output, in terms of their properties:
    "I have an object whose keys are strings that represent IDs, and whose values are objects that represent users. I want an array whose values are user objects, but only those of users who are registered."
  2. Figure out how to iterate over the object in some way:
    "I know that I can obtain an array of the keys in an object by calling Object.keys() on that object. I want an array because arrays support iteration."
  3. Recognize that this method gives you keys and that you want to transform those keys into values, and recognize that map is an obvious method for creating a new array by transforming the values of another array:
    Object.keys(usersById).map(id => usersById[id]).
  4. Recognize that you now have an array of user objects, and that the array is filterable and contains the actual values you would like to filter:
    Object.keys(usersById).map(id => usersById[id]).filter(user => user.registered).

Had Bill taken this approach, we might be talking about his employment start date.

Why don't people use fundamentals more?

Sometimes people simply aren't aware. Often, they are too busy and don't take the time to learn how to solve problems in this manner; they just need to get things done. This risk with this approach is that it becomes a habit and ultimately a obstacle hindering further improvement.

To avoid these pitfalls, always start with the fundamentals. At every step of the process, stop and think about what kind of data you are dealing with. Rather than simply relying on patterns you've seen before, always consider the primitive data type you have on hand: an array, an object, a string, and so on. When you use a function or method, reference its documentation so you know exactly what data types support it, what arguments it takes and their types, and what it returns.

In this manner, you can produce a working solution on the first try. You can be confident that it is correct, because you deliberately chose your techniques based on the given input and the desired output. Remain aware of exactly what every operation does in terms of the basics (data types and return values) rather than vague business-level ideas ("registered users").

Recommendations for aspiring senior developers

Want to get better at the fundamentals? Check out my next post: 5 tips to improve your development approach by focusing on fundamentals.

 

+++

We're hiring! If you're looking for an opportunity to work with an exceptional team of fundamentals developers focused on engineering forward-looking solutions, please check out our open positions today.