> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/getify/You-Dont-Know-JS/llms.txt
> Use this file to discover all available pages before exploring further.

# Appendix B: Practice

> Test your knowledge with hands-on exercises covering scope chains, closures, and modules. Includes suggested solutions to help you master JavaScript scope concepts.

This appendix aims to give you some challenging and interesting exercises to test and solidify your understanding of the main topics from this book.

<Warning>
  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!
</Warning>

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:

<Steps>
  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.
</Steps>

<Tip>
  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.
</Tip>

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:

```javascript theme={null}
isPrime(11);        // true
isPrime(12);        // false

factorize(11);      // [ 11 ]
factorize(12);      // [ 3, 2, 2 ] --> 3*2*2=12
```

Here's an implementation of `isPrime(..)`:

```javascript theme={null}
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(..)`:

```javascript theme={null}
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!

<CardGroup cols={2}>
  <Card title="Part 1" icon="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.
  </Card>

  <Card title="Part 2" icon="2">
    Use the same closure cache technique for `factorize(..)`.
  </Card>
</CardGroup>

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**.

<Warning>
  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.
</Warning>

This can definitely be worth the expense, but only if we think it's likely we see repetition of common inputs.

<Tip>
  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.
</Tip>

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.

```javascript theme={null}
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"
```

<Note>
  The corner case of passing in no values to `toggle(..)` is not very important; such a toggler instance could just always return `undefined`.
</Note>

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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
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;
}
```

<Tip>
  Don't get too mired in calculator-specific behavior. Focus on the *memory* of closure.
</Tip>

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:

<CardGroup cols={3}>
  <Card title="number()" icon="hashtag">
    Input: the character/number "pressed"
  </Card>

  <Card title="Operators" icon="plus">
    `plus()`, `minus()`, `mult()`, `div()`
  </Card>

  <Card title="eq()" icon="equals">
    Compute the result
  </Card>
</CardGroup>

Usage would look like:

```javascript theme={null}
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:

```javascript theme={null}
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.

<Tip>
  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.
</Tip>

## 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

```javascript theme={null}
// 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)

```javascript theme={null}
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)

```javascript theme={null}
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)

```javascript theme={null}
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

```javascript theme={null}
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.
