§4.3.

Simplicity in your code

In this section, I will explore what it means to write simple code.

Consider the following two programs. They compare the averages of two arrays to answer a question: is the average of the first array greater than the average of the second array?

Example 1. Comparison of averages
function firstHasGreaterAverage(f, s) {
    let ft = 0;
    let st = 0;
    for (let i=0; i<f.length; i++) {
        ft = ft + f[i];
    }
    for (let i=0; i<s.length; i++) {
        st = st + s[i];
    }
    return ft/f.length > st/f.length;
}

let x = [10,14,48,3];
let y = [49,5,20,5,22];

if (firstHasGreaterAverage(x, y))
    console.log('x is greater');
else
    console.log('x is not greater');
Example 2. Alternative comparison of averages
function average(items) {
    let total = 0;
    for (let i=0; i<items.length; i++) {
        total = total + items[i];
    }
    return total / items.length;
}

function firstHasGreaterAverage(first, second) {
    return average(first) > average(second);
}

let x = [10,14,48,3];
let y = [49,5,20,5,22];

if (firstHasGreaterAverage(x, y))
    console.log('x is greater');
else
    console.log('x is not greater');

Which is “simpler”?

Both implementations have the same number of lines of code.

Example 1 is arguably simpler at first glance: it uses only one function, and the variable names are shorter. However, this does not match the experience of reading the code. Example 1 is more difficult to understand.

So, what makes Example 2 simpler?

  1. It uses a smaller number of “rules”: Example 1 duplicates the formula for computing an average; Example 2 uses average as a reusable function.

  2. It uses self-explanatory variable names: Example 1 requires effort to recognized that ft is an abbreviation of “first total”; Example 2 does not require effort to understand.

Recognizing simplicity in code involves a design trade-off. But, does shorter always mean simpler?

I could simplify the code even further:

Example 3. Shorter comparison of averages
let x = [10,14,48,3];
let y = [49,5,20,5,22];

const average = (list) => list.reduce((acc,curr) => acc + curr, 0) / list.length;
console.log(
    'x',
    average(x) > average(y) ? 'is' : 'is not',
    'greater'
);

This code is shorter: it uses fewer lines of code. Is it simpler?

The code uses sophisticated JavaScript features. An experienced team of developers might be comfortable with calculating the average with a single line of code [1] and the ternary operator ( …​ ? …​ : …​ ).

However, the sophisticated features used in this code are not easy to read. An experienced programmer will need first to recognize that reduce is being used to compute the sum and then carefully check the use of the ternary operator. Example 3 is correct, but it is not obviously correct to an experienced developer.

I try to find a balance. I try to simplify where I can while ensuring that the entire code remains easy to understand.

To find a balance, I identify the parts of the code that I don’t like. I’ve emphasized the sections of Example 1 that I feel could be improved:

function firstHasGreaterAverage(f, s) {
    let ft = 0;
    let st = 0;
    for (let i=0; i<f.length; i++) {
        ft = ft + f[i];
    }
    for (let i=0; i<s.length; i++) {
        st = st + s[i];
    }
    return ft/f.length > st/f.length;
}

let x = [10,14,48,3];
let y = [49,5,20,5,22];

if (firstHasGreaterAverage(x, y))
    console.log('x is greater');
else
    console.log('x is not greater');

I don’t like the two for-loops because they were nearly identical and difficult to understand. I don’t like the name of the method firstHasGreaterAverage because it sounded unnatural and long. I also don’t like the repetition in the output.

To improve the code, I then experimented with different versions of the code: I tried all the ideas you’ve seen in this section and a few other ideas. [2] With each version, I noticed how I felt about different parts of the code. Eventually, I took the best from each to create a final version: [3]

Example 4. Improved comparison of averages
// Average of a non-empty array
// an empty array returns NaN
function average(items) {
    let total = 0;
    for (let item of items) {
        total = total + items[i];
    }
    return total / items.length;
}

let x = [10,14,48,3];
let y = [49,5,20,5,22];

if (average(x) > average(y))
    console.log('x is greater');
else
    console.log('x is not greater');

I’m still not 100% happy with the final code. There is still repetition in the two output lines 'x is greater' and 'x is not greater'. Other approaches — particularly the ternary operator — can remove the repetition. However, the result is worse: removing the repetition makes the code more difficult to understand.

In the end, I decided to stay with the repetition because it was the most 'boring' (and therefore less likely to be confusing or surprising).

The final version of this code in Example 4 may appear to be plain, obvious and unexciting. This plainness is a sign that I have achieved simplicity!

Exercise: Text in a frame

Revisit your solutions to the ‘text in a frame’ warmup exercise in Chapter 2.

Can you redesign the code to make it more simple?

  • Identify the parts of the code that you dislike

  • Experiment with different ways of writing those lines

  • Keep experimenting until you are happy with the finished product

Exercise: Roman numerals

Write a function that converts the integers from 1 to 1000 into roman numerals (e.g., convert(23) returns 'XXIII').

Try to solve this problem in three ways:

  1. The first solution that you can get to work

  2. A clever or tricky solution

  3. A final solution that you are most happy about

Reflection: Simplicity in practice

How long did it take you to implement a working solution to a programming problem? How long did it take you to turn that working version into a working solution that is also simple? Do you think the ratio of the duration that you invested in these two efforts is typical of other projects?


1. This style of code using map and reduce functions with callbacks is sometimes referred to as a functional programming style.
2. Another bad idea was this obscure code: const average = (list) => eval(list.join('+')) / list.length;
3. This isn’t perfect yet. It doesn’t handle empty lists ([]). A pedantic reader might also notice numerical stability problems: tiny numerical errors can accumulate if many small numbers are averaged.