# Exercise: Recipe Scaler

Finish a recipe scaler split into modules, which converts cups to milliliters, scales a recipe from 4 servings to 10 and refuses bad servings with a ServingsError, and let the tests check each piece and each error.

Journeyman · JavaScript · Software Development · Updated October 30, 2026

The *Saturday Pancakes* card serves 4, and you're making brunch for 10. Your friend who's coming from overseas cooks in milliliters, not cups. And someone will, sooner or later, type `-2` or `ten` into the servings box. In this exercise you finish a small recipe scaler that handles all three: it's split into modules, it converts units, and it refuses servings it can't use with an error of its own. Tests check every function, and every error.

## Get the exercise code

In the **Exercise** box beside the lesson, choose **Get the starter on GitHub** to open its GitHub repository, `exercise-javascript-recipe-scaler`. Click **Code**, then **Download ZIP**, and unzip it. Or clone it with Git. Then open the folder in VS Code.

Each file has one job:

- `units.js`: kitchen measurements. `ML_PER_UNIT` (240 ml in a cup, 15 in a tablespoon, 5 in a teaspoon), `round` and `isVolume` are ready to use. You finish `toMilliliters`.
- `scale.js`: the scaler. You write its import line, the `ServingsError` class, `checkServings`, `scaleIngredients` and `toMetric`.
- `main.js`: prints the pancake card scaled for 10. Ready to use, except the last problem.
- `recipe.js`: the recipe card itself, as a default export. Ready to use.
- `units.test.js`, `scale.test.js`, `main.test.js`: the tests. You don't need to change these.
- `package.json`: says `"type": "module"`, so every `.js` file here is a module.

## What to do

Each function you finish is already there, with its parameters, and returns a placeholder for now. The comment above it says what it should do, with examples. Here's one, from `scale.js`:

```javascript
/*
 * 3. Check a number of servings. Return it when it's a whole number above 0.
 *    Otherwise throw a ServingsError whose message says what's wrong and shows the value that came in.
 *
 *    checkServings(10)      -> 10
 *    checkServings(-2)      -> throws ServingsError: Servings must be a whole number above 0, but got -2.
 *    checkServings(2.5)     -> throws ServingsError (half a serving isn't a serving)
 *    checkServings("ten")   -> throws ServingsError: Servings must be a whole number above 0, but got ten.
 */
export function checkServings(servings) {
  return servings;
}
```

Work through the problems in order:

1. **Problem 0:** at the top of `scale.js`, import `round`, `isVolume` and `toMilliliters` from `"./units.js"`.
2. **Problem 1:** finish `toMilliliters` in `units.js`. A unit it doesn't know, such as `"pinch"`, throws an `Error` whose message names the unit.
3. **Problem 2:** finish the `ServingsError` class, so its `name` is `"ServingsError"` and it keeps its message.
4. **Problems 3 to 5:** finish `checkServings`, then `scaleIngredients` (which uses it), then `toMetric`. The last two return new lists and leave the one they're given as it is.
5. **Problem 6:** at the bottom of `main.js`, scale the pancakes to `"ten"` servings inside a `try`, and in the `catch`, print the error's name and message as two lines, `6) name: ...` and `7) message: ...`. The program keeps going after it.

## Check your work

In VS Code's terminal (**Terminal > New Terminal**), from the exercise folder, run the tests:

```bash
node --test
```

At first most tests fail. That's expected: each problem you finish turns its tests green. When a test fails, its message says what it tried and what it expected, for example *checkServings(-2) should throw a ServingsError, but it didn't throw anything.*

To see the program itself, run:

```bash
node main.js
```

## Hints

- **Work out one example by hand.** From 4 servings to 10, every amount is multiplied by the same number. What is it?
- **Two kinds of error, two lessons.** `toMilliliters` throws a plain `Error`; `checkServings` throws your own `ServingsError`. *Throwing Your Own Errors* shows both, and the `super(message)` line a custom error needs.
- **Use what's ready.** `isVolume(unit)` tells you whether a unit can be converted, and `round(amount)` rounds to 2 decimal places. They're in `units.js`, so `scale.js` imports them, as in *Modules: import and export*.
- **A new list, not a changed one.** If a test says the list it gave you was changed, look for a line that sets `item.amount = ...`. Build a new object for each ingredient instead, as in *Spread and Rest*.
- **Lost?** Set a breakpoint inside the function you're working on, start the debugger on `main.js`, and step through it while you watch the values, as in *Debugging with Breakpoints*.

## When you're done

Every test is green, and `node main.js` prints:

```text
Saturday Pancakes, for 10 (the card serves 4)
  flour: 5 cups
  milk: 3.75 cups
  eggs: 5 whole
  sugar: 5 tbsp
  baking powder: 5 tsp
  salt: 1.25 tsp
The same, in milliliters
  flour: 1200 ml
  milk: 900 ml
  eggs: 5 whole
  sugar: 75 ml
  baking powder: 25 ml
  salt: 6.25 ml
6) name: ServingsError
7) message: Servings must be a whole number above 0, but got ten.
Enjoy your brunch!
```

Now look back at your error messages. If a friend typed `0` into the servings box and saw your message, would they know what to type instead?

## So what

Splitting a program into modules, checking every value at the door and throwing errors that say what to fix: that's how real apps stay working when people use them in ways nobody planned.

## Key terms

- [ES Module](https://www.ask-codey.com/glossary/es-module)
- [Named Export](https://www.ask-codey.com/glossary/named-export)
- [throw](https://www.ask-codey.com/glossary/throw-javascript)
- [Custom Error](https://www.ask-codey.com/glossary/custom-error)
- [try Block](https://www.ask-codey.com/glossary/try-block-javascript)
- [catch Block](https://www.ask-codey.com/glossary/catch-block-javascript)

## Related

- [Handling Errors with try, catch and finally](https://www.ask-codey.com/lessons/javascript-errors-try-catch-finally)
- [Throwing Your Own Errors](https://www.ask-codey.com/lessons/javascript-errors-throwing-your-own)
- [Modules: import and export](https://www.ask-codey.com/lessons/javascript-modules-import-and-export)
- [Debugging with Breakpoints](https://www.ask-codey.com/lessons/javascript-debugging-breakpoints)

Source: https://www.ask-codey.com/lessons/exercise-javascript-recipe-scaler
