Skip to main content
This appendix aims to give you some challenging and interesting exercises to test and solidify your understanding of the main topics from this book.
It’s a good idea to try out the exercises yourself—in an actual code editor!—instead of skipping straight to the solutions at the end. No cheating!
These exercises don’t have a specific right answer that you have to get exactly. Your approach may differ some (or a lot!) from the solutions presented, and that’s OK.

Buckets of Marbles

This exercise asks you to write a program—any program!—that contains nested functions and block scopes, which satisfies these constraints:
  1. If you color all the scopes (including the global scope!) different colors, you need at least six colors. Add a code comment labeling each scope with its color.
    • BONUS: identify any implied scopes your code may have.
  2. Each scope has at least one identifier.
  3. Contains at least two function scopes and at least two block scopes.
  4. At least one variable from an outer scope must be shadowed by a nested scope variable.
  5. At least one variable reference must resolve to a variable declaration at least two levels higher in the scope chain.
You can just write junk foo/bar/baz-type code for this exercise, but I suggest you try to come up with some sort of non-trivial real’ish code that does something kind of reasonable.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.

Closure (PART 1)

Let’s practice closure with some common computer-math operations: determining if a value is prime, and generating a list of prime factors. For example:
isPrime(11);        // true
isPrime(12);        // false

factorize(11);      // [ 11 ]
factorize(12);      // [ 3, 2, 2 ] --> 3*2*2=12
Here’s an implementation of isPrime(..):
function isPrime(v) {
    if (v <= 3) {
        return v > 1;
    }
    if (v % 2 == 0 || v % 3 == 0) {
        return false;
    }
    var vSqrt = Math.sqrt(v);
    for (let i = 5; i <= vSqrt; i += 6) {
        if (v % i == 0 || v % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}
And here’s a basic implementation of factorize(..):
function factorize(v) {
    if (!isPrime(v)) {
        let i = Math.floor(Math.sqrt(v));
        while (v % i != 0) {
            i--;
        }
        return [
            ...factorize(i),
            ...factorize(v / i)
        ];
    }
    return [v];
}
If you call isPrime(4327) multiple times in a program, it goes through all its computation steps every time. That’s a lot of wasted work!

Part 1

Use closure to implement a cache to remember the results of isPrime(..), so that the primality of a given number is only ever computed once.

Part 2

Use the same closure cache technique for factorize(..).
Use separate closures for caching of isPrime(..) and factorize(..).

A Word About Memory

We can see that saving repeated calls improves computation speed. But this usage of closure is making an explicit trade-off: memory.
We’re essentially growing our cache (in memory) unboundedly. If the functions were called many millions of times with mostly unique inputs, we’d be chewing up a lot of memory.
This can definitely be worth the expense, but only if we think it’s likely we see repetition of common inputs.
It might be a good idea to have a more sophisticated caching approach, such as an LRU (least recently used) cache, that limits its size. As it runs up to the limit, an LRU evicts the values that are least recently used.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.

Closure (PART 2)

In this exercise, we’re going to practice closure by defining a toggle(..) utility that gives us a value toggler. You will pass one or more values (as arguments) into toggle(..), and get back a function. That returned function will alternate/rotate between all the passed-in values in order, one at a time, as it’s called repeatedly.
function toggle(/* .. */) {
    // ..
}

var hello = toggle("hello");
var onOff = toggle("on","off");
var speed = toggle("slow","medium","fast");

hello();      // "hello"
hello();      // "hello"

onOff();      // "on"
onOff();      // "off"
onOff();      // "on"

speed();      // "slow"
speed();      // "medium"
speed();      // "fast"
speed();      // "slow"
The corner case of passing in no values to toggle(..) is not very important; such a toggler instance could just always return undefined.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.

Closure (PART 3)

In this third and final exercise on closure, we’re going to implement a basic calculator. The calculator() function will produce an instance of a calculator that maintains its own state:
function calculator() {
    // ..
}

var calc = calculator();
Each time calc(..) is called, you’ll pass in a single character that represents a keypress of a calculator button. We’ll restrict our calculator to:
  • Digits (0-9)
  • Arithmetic operations (+, -, *, /)
  • ”=” to compute the operation
Operations are processed strictly in the order entered; there’s no ”( )” grouping or operator precedence. For example:
calc("4");     // 4
calc("+");     // +
calc("7");     // 7
calc("3");     // 3
calc("-");     // -
calc("2");     // 2
calc("=");     // 75
calc("*");     // *
calc("4");     // 4
calc("=");     // 300
Here’s a useCalc(..) helper that runs the calculator with characters from a string:
function useCalc(calc,keys) {
    return [...keys].reduce(
        function showDisplay(display,key){
            var ret = String( calc(key) );
            return (
                display +
                (
                  (ret != "" && key == "=") ?
                      "=" :
                      ""
                ) +
                ret
            );
        },
        ""
    );
}

useCalc(calc,"4+3=");           // 4+3=7
useCalc(calc,"+9=");            // +9=16
useCalc(calc,"*8=");            // *5=128
useCalc(calc,"7*2*3=");         // 7*2*3=42
Here’s a formatTotal(..) function your calculator should use:
function formatTotal(display) {
    if (Number.isFinite(display)) {
        let maxDigits = 11;
        if (Math.abs(display) > 99999999999) {
            maxDigits -= 6;
        }
        if (display < 0) {
            maxDigits--;
        }

        if (Number.isInteger(display)) {
            display = display
                .toPrecision(maxDigits)
                .replace(/\.0+$/,"");
        }
        else {
            maxDigits--;
            if (
                Math.abs(display) >= 0 &&
                Math.abs(display) < 1
            ) {
                maxDigits--;
            }
            display = display
                .toPrecision(maxDigits)
                .replace(/0+$/,"");
        }
    }
    else {
        display = "ERR";
    }
    return display;
}
Don’t get too mired in calculator-specific behavior. Focus on the memory of closure.
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.

Modules

This exercise is to convert the calculator from Closure (PART 3) into a module. We’re not adding any additional functionality, only changing its interface. Instead of calling a single function calc(..), we’ll be calling specific methods on the public API for each “keypress.” This module should be expressed as a classic module factory function called calculator(), so that multiple calculators can be created if desired. The public API should include:

number()

Input: the character/number “pressed”

Operators

plus(), minus(), mult(), div()

eq()

Compute the result
Usage would look like:
var calc = calculator();

calc.number("4");     // 4
calc.plus();          // +
calc.number("7");     // 7
calc.number("3");     // 3
calc.minus();         // -
calc.number("2");     // 2
calc.eq();            // 75
Here’s the updated useCalc(..) helper:
function useCalc(calc,keys) {
    var keyMappings = {
        "+": "plus",
        "-": "minus",
        "*": "mult",
        "/": "div",
        "=": "eq"
    };

    return [...keys].reduce(
        function showDisplay(display,key){
            var fn = keyMappings[key] || "number";
            var ret = String( calc[fn](key) );
            return (
                display +
                (
                  (ret != "" && key == "=") ?
                      "=" :
                      ""
                ) +
                ret
            );
        },
        ""
    );
}

useCalc(calc,"4+3=");           // 4+3=7
useCalc(calc,"+9=");            // +9=16
Try the exercise for yourself, then check out the suggested solution at the end of this appendix.
As you work on this exercise, spend some time considering the pros/cons of representing the calculator as a module as opposed to the closure-function approach from the previous exercise.BONUS: Write out a few sentences explaining your thoughts.BONUS #2: Try converting your module to other formats: UMD, CommonJS, and ESM.

Suggested Solutions

Remember, each suggested solution is just one way to approach the problems. They’re not “the right answer,” but they do illustrate a reasonable approach.

Suggested: Buckets of Marbles

// RED(1)
const howMany = 100;

// Sieve of Eratosthenes
function findPrimes(howMany) {
    // BLUE(2)
    var sieve = Array(howMany).fill(true);
    var max = Math.sqrt(howMany);

    for (let i = 2; i < max; i++) {
        // GREEN(3)
        if (sieve[i]) {
            // ORANGE(4)
            let j = Math.pow(i,2);
            for (let k = j; k < howMany; k += i) {
                // PURPLE(5)
                sieve[k] = false;
            }
        }
    }

    return sieve
        .map(function getPrime(flag,prime){
            // PINK(6)
            if (flag) return prime;
            return flag;
        })
        .filter(function onlyPrimes(v){
            // YELLOW(7)
            return !!v;
        })
        .slice(1);
}

findPrimes(howMany);
// [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, ... ]

Suggested: Closure (PART 1)

var isPrime = (function isPrime(v){
    var primes = {};

    return function isPrime(v) {
        if (v in primes) {
            return primes[v];
        }
        if (v <= 3) {
            return (primes[v] = v > 1);
        }
        if (v % 2 == 0 || v % 3 == 0) {
            return (primes[v] = false);
        }
        let vSqrt = Math.sqrt(v);
        for (let i = 5; i <= vSqrt; i += 6) {
            if (v % i == 0 || v % (i + 2) == 0) {
                return (primes[v] = false);
            }
        }
        return (primes[v] = true);
    };
})();

var factorize = (function factorize(v){
    var factors = {};

    return function findFactors(v) {
        if (v in factors) {
            return factors[v];
        }
        if (!isPrime(v)) {
            let i = Math.floor(Math.sqrt(v));
            while (v % i != 0) {
                i--;
            }
            return (factors[v] = [
                ...findFactors(i),
                ...findFactors(v / i)
            ]);
        }
        return (factors[v] = [v]);
    };
})();
The general steps:
  1. Wrap an IIFE to define the scope for the cache variable
  2. In the underlying call, first check the cache
  3. At each return, assign to the cache and return that result

Suggested: Closure (PART 2)

function toggle(...vals) {
    var unset = {};
    var cur = unset;

    return function next(){
        // save previous value back at the end of the list
        if (cur != unset) {
            vals.push(cur);
        }
        cur = vals.shift();
        return cur;
    };
}

var hello = toggle("hello");
var onOff = toggle("on","off");

hello();      // "hello"
hello();      // "hello"

onOff();      // "on"
onOff();      // "off"
onOff();      // "on"

Suggested: Closure (PART 3)

function calculator() {
    var currentTotal = 0;
    var currentVal = "";
    var currentOper = "=";

    return pressKey;

    function pressKey(key){
        // number key?
        if (/\d/.test(key)) {
            currentVal += key;
            return key;
        }
        // operator key?
        else if (/[+*/-]/.test(key)) {
            if (
                currentOper != "=" &&
                currentVal != ""
            ) {
                pressKey("=");
            }
            else if (currentVal != "") {
                currentTotal = Number(currentVal);
            }
            currentOper = key;
            currentVal = "";
            return key;
        }
        // = key?
        else if (
            key == "=" &&
            currentOper != "="
        ) {
            currentTotal = op(
                currentTotal,
                currentOper,
                Number(currentVal)
            );
            currentOper = "=";
            currentVal = "";
            return formatTotal(currentTotal);
        }
        return "";
    };

    function op(val1,oper,val2) {
        var ops = {
            "+": (v1,v2) => v1 + v2,
            "-": (v1,v2) => v1 - v2,
            "*": (v1,v2) => v1 * v2,
            "/": (v1,v2) => v1 / v2
        };
        return ops[oper](val1,val2);
    }
}

Suggested: Modules

function calculator() {
    var currentTotal = 0;
    var currentVal = "";
    var currentOper = "=";

    var publicAPI = {
        number,
        eq,
        plus() { return operator("+"); },
        minus() { return operator("-"); },
        mult() { return operator("*"); },
        div() { return operator("/"); }
    };

    return publicAPI;

    function number(key) {
        if (/\d/.test(key)) {
            currentVal += key;
            return key;
        }
    }

    function eq() {
        if (currentOper != "=") {
            currentTotal = op(
                currentTotal,
                currentOper,
                Number(currentVal)
            );
            currentOper = "=";
            currentVal = "";
            return formatTotal(currentTotal);
        }
        return "";
    }

    function operator(key) {
        if (
            currentOper != "=" &&
            currentVal != ""
        ) {
            eq();
        }
        else if (currentVal != "") {
            currentTotal = Number(currentVal);
        }
        currentOper = key;
        currentVal = "";
        return key;
    }

    function op(val1,oper,val2) {
        var ops = {
            "+": (v1,v2) => v1 + v2,
            "-": (v1,v2) => v1 - v2,
            "*": (v1,v2) => v1 * v2,
            "/": (v1,v2) => v1 / v2
        };
        return ops[oper](val1,val2);
    }
}

That’s it for this book! Congratulations on your achievement. When you’re ready, move on to the next book in the series.

Build docs developers (and LLMs) love