Why you can't just double a recipe
Eggs, salt, pans and bake times: what multiplies and what doesn't, plus code for kitchen-friendly rounding.
You have a recipe for four. Eight people are coming. Easy, right? Multiply everything by two and get cooking.
Most of the time, that works. Flour, butter, onions, stock: double them and you're fine. Then one day the cake sinks in the middle, the rice turns to mush, and the curry is somehow too hot. Scaling a recipe is mostly maths. The rest is cooking.
Here's where it goes wrong, how to round amounts so a person can actually measure them, and some code if you're building a servings slider.
What doesn't scale
Eggs. Take a recipe for four with one egg and make it for six. You need 1½ eggs. You can't crack half an egg, but you can beat one and use half of it. A large egg is about 50 g out of its shell, so beat it and weigh out what you need. In a stew or a meatball mix, just round to the nearest whole egg. In a cake, the extra or missing half egg can change the texture, so it's worth the fuss.
Salt and spices. Multiply them, but hold a little back. Add the last bit after tasting. That goes double for chilli and strong spices. You can always add more salt. You can't take it out.
Pan size and depth. This is the big one for baking. A doubled cake batter in a bigger pan isn't the same cake. It's deeper, so the middle takes longer to cook while the edges dry out. America's Test Kitchen puts it simply: bake time and temperature depend mostly on the depth and surface area of the batter. King Arthur has a nice example. Doubling a 9 x 13 inch brownie recipe into a half-sheet pan can overflow, because a half sheet is only 1 inch deep and a 9 x 13 is usually 2. Their fix is to make 1½ times the recipe instead. That fills the pan about right and the baking time didn't change.
Bake time. It doesn't double. If you bake two pans of the original size, the time stays about the same. You might need a few extra minutes with a fuller oven, so start checking at the original time. If you bake one bigger, deeper cake, it will take longer, and you'll have to go by the toothpick, not the clock.
Leavening. Baking soda and baking powder are where people get nervous. Here's the good news. If you keep the same pan size, as in two loaves instead of one, the chemistry scales with everything else. The trouble starts when the batter ends up deeper or sits around waiting for the oven. So the safest way to double a cake or a quick bread is two batches, or two pans. That's ATK's advice too.
Evaporation. Liquid boils off from the surface, and a double batch in the same pot has the same surface. So it loses about the same amount of water, not twice as much. ATK found this with rice. Their double batch of rice pilaf uses 3 cups of rice and 3¾ cups of water, not the 4½ cups straight doubling would give you. Soups, stews and sauces work the same way. They take longer to thicken, and you may need less liquid than you think.
Your kit. A doubled dough can overload a stand mixer. A doubled stew might not fit the pot. Look at the size before you start.
The other problem is the numbers. Scale "1 cup flour" from six servings to two and you get 0.333 cups. Scale "2½ tsp cumin" from four to twelve and you get 7.5 tsp. Both are correct. Neither is how anyone measures.
A cook would write ⅓ cup and 2½ tbsp. The rules are simple once you see them. A measuring cup is marked in quarters and thirds. Spoons come in eighths, quarters and halves of a teaspoon, and in tablespoons. Kitchen scales read to the gram, and a gram either way doesn't matter once you're past 50. And when an amount gets big in small units, you switch to a bigger one.
Here's a small TypeScript function that does that. It scales one amount, picks the spoon or cup that reads best, and rounds to marks you can actually find in a drawer:
type Unit = 'cup' | 'g' | 'ml' | 'piece' | 'tbsp' | 'tsp'
interface Amount {
unit: Unit
value: number
}
// The marks on each measure, as fractions of one.
const marks = {
cup: [0, 1 / 4, 1 / 3, 1 / 2, 2 / 3, 3 / 4, 1],
tbsp: [0, 1 / 2, 1],
tsp: [0, 1 / 8, 1 / 4, 1 / 2, 3 / 4, 1]
}
const teaspoonsIn = { cup: 48, tbsp: 3, tsp: 1 }
// Snaps the part after the whole number to the nearest mark.
function snap(value: number, steps: number[]) {
const whole = Math.floor(value)
const rest = value - whole
const nearest = steps.reduce((best, step) =>
Math.abs(step - rest) < Math.abs(best - rest) ? step : best
)
return whole + nearest
}
// Picks the measure that reads best, then rounds to its marks.
function roundSpoons(teaspoons: number): Amount {
const unit = teaspoons >= 12 ? 'cup' : teaspoons >= 3 ? 'tbsp' : 'tsp'
const value = snap(teaspoons / teaspoonsIn[unit], marks[unit])
return { unit, value: Math.max(value, 1 / 8) }
}
// A gram under 50, five up to a kilo, ten after that.
function roundMetric(value: number) {
const step = value < 50 ? 1 : value < 1000 ? 5 : 10
return Math.max(Math.round(value / step) * step, 1)
}
// Eggs, cloves and onions go to the nearest half.
function roundCount(value: number) {
return Math.max(Math.round(value * 2) / 2, 0.5)
}
export function scale(amount: Amount, from: number, to: number): Amount {
const value = amount.value * (to / from)
const unit = amount.unit
switch (unit) {
case 'g':
case 'ml':
return { unit, value: roundMetric(value) }
case 'piece':
return { unit, value: roundCount(value) }
default:
return roundSpoons(value * teaspoonsIn[unit])
}
}
const glyphs: [number, string][] = [
[1 / 8, '⅛'],
[1 / 4, '¼'],
[1 / 3, '⅓'],
[1 / 2, '½'],
[2 / 3, '⅔'],
[3 / 4, '¾']
]
export function format({ unit, value }: Amount) {
const whole = Math.floor(value)
const glyph = glyphs.find(([fraction]) => Math.abs(value - whole - fraction) < 0.01)?.[1]
const number = glyph ? `${whole || ''}${glyph}` : String(Number(value.toFixed(2)))
const label = unit === 'cup' && value > 1 ? 'cups' : unit
return unit === 'piece' ? number : `${number} ${label}`
}
And here it is at work:
format(scale({ unit: 'tsp', value: 2.5 }, 4, 12)) // "2½ tbsp"
format(scale({ unit: 'cup', value: 1 }, 6, 2)) // "⅓ cup"
format(scale({ unit: 'tbsp', value: 1 }, 9, 3)) // "1 tsp"
format(scale({ unit: 'piece', value: 1 }, 4, 6)) // "1½"
format(scale({ unit: 'g', value: 125 }, 4, 6)) // "190 g"
The trick is that everything with a spoon or a cup goes through teaspoons first. That's what lets 7.5 tsp come out as 2½ tbsp. Notice what it doesn't do, though. It happily says 1½ eggs. It doesn't know salt should be held back, or that your pan just got too small. Those are cooking problems, and a function can't see the pan. The honest fix is to show a short note next to the slider when someone scales a baking recipe: "Baking? Use two pans of the original size."
Using servings from the API
If you're getting recipes from Tiny Plates, you don't need to write the maths yourself. Add servings to a recipe request and every quantity is rewritten for you:
curl 'https://api.tinyplates.dev/recipes/69e22c71d0295ac8a9011ecd?servings=2' \
-H 'Authorization: Bearer rd_your_api_key'
That's a banana bread that makes one loaf, asked for two. servings is counted in whatever the recipe makes, so here it means loaves. It has to be a whole number from 1 to 100. Here's part of what comes back:
[
{ "display": "3½ cups all-purpose flour, spooned into measuring cup and leveled-off", "original": "1¾ cups all-purpose flour, (spooned into measuring cup and leveled-off)" },
{ "display": "2 tsp baking soda", "original": "1 teaspoon baking soda" },
{ "display": "4 large eggs", "original": "2 large eggs" },
{ "display": "1 cup unsalted butter, softened (1 stick)", "original": "1/2 cup (1 stick) unsalted butter, softened" }
]
A few good things happen. Ranges scale at both ends. An ingredient with no amount, like "vegetable oil, for cooking", is left alone rather than given a made-up number. The line the source wrote stays in original. Per-serving nutrition stays the same, because more servings of the same food is the same plate. The whole-recipe totals in nutritionTotals do change. And amounts are rounded once, at the very end. Grams and millilitres go to steps a scale can read. Spoons go to eighths, cups to the fractions on a measuring cup, and countable things like eggs and garlic cloves to halves.
Add units=metric and the flour comes back in grams, which is the best fix for scaling baking recipes there is. There's more on that in cups to grams. If a recipe doesn't say how many servings it makes, you get a clear error instead of a guess.
Now the honest part. Look at that butter line. The cup doubled, but the "(1 stick)" note didn't. Notes and preparation text are kept as the source wrote them. The steps aren't rewritten either, so the doubled banana bread still says to use a 9 x 5 inch loaf pan and bake for 40 to 45 minutes. That's right if you bake two loaves, and wrong if you pour it all into one big pan. Times in the response stay the same too.
Our rounding also keeps the unit the recipe used. Scale ricotta pancakes from nine down to three and "1 tablespoon vanilla extract" comes back as "⅜ tbsp". That's correct and measurable (it's just over a teaspoon), but a person would write "1 tsp". If you want that, run our amounts through a function like the one above. And yes, the API will also tell you to use 1½ eggs.
You can scale a whole week at once too. In /shopping-list, put :servings after a recipe id, like recipes=69e22c71d0295ac8a9011ecd:2,6a9820e58f8122ec891b68a4. Each recipe is scaled first, then everything is added up into one list. We wrote about that in one shopping list for a week of recipes, and the weekly meal planner is a good place to put a servings slider.
So, can you just double a recipe? For dinner, mostly yes. Taste as you go and give it a bit longer to reduce. For baking, double the ingredients and use two pans. Your servings slider can do the maths. The pan is still up to you.
If you want to try the API's scaling, grab a free key. It's 50 requests a day and no credit card.