I kept seeing delivery-versus-cooking comparisons that stopped at the receipt total. That misses the time spent shopping, cooking, cleaning, waiting, or walking downstairs. I built this calculator to show both answers: cash-only cost and cash plus an explicit opportunity cost for time. It is for someone making an ordinary weeknight decision, not for proving that one lifestyle is always financially superior.
Keep money and time as separate computed totals
Delivery money is the meal amount plus delivery fee, service fee, and tip. Cooking money is ingredient cost. The component then computes time costs from minutes and the user's time value per hour:
const deliveryMoney = computed(() =>
safeNumber(state.mealAmount)
+ safeNumber(state.deliveryFee)
+ safeNumber(state.serviceFee)
+ safeNumber(state.tip)
);
const cookingMoney = computed(() =>
safeNumber(state.ingredientCost)
);
const deliveryTimeCost = computed(() =>
(safeNumber(state.deliveryWaitMinutes) / 60)
* safeNumber(state.timeValuePerHour)
);
Cooking time combines shopping, cooking, and cleaning. With a 220-per-hour time value, 12 minutes of delivery wait costs 44 in the model. Twenty minutes shopping, 25 cooking, and 10 cleaning costs 201.67 of time. Those numbers are not charges on a credit card; they are an explicit way to ask what else that time could have done.
The component keeps deliveryMoney, cookingMoney, deliveryTimeCost, and cookingTimeCost separate before adding them into deliveryTotal and cookingTotal. That makes the screen explain why a result changes. safeNumber clamps invalid and negative values to zero, which protects calculations but means a negative correction cannot represent a refund. Inputs are also not a nutrition or quality database; the user supplies the facts.
The “flip” is a useful diagnostic
The page compares moneyWinner from the cash difference with totalWinner from the money-plus-time difference. If they differ, it shows that the conclusion flipped after opportunity cost was added. Consider delivery cash of 340 and cooking cash of 120: cooking wins by 220. If cooking takes 55 minutes and delivery takes 12, the value of time can exceed that 220 gap, making delivery the lower total-cost choice.
That is not a universal answer about delivery. It is a signal that the decision depends on how the reader values a particular evening. Set time value to zero and the total-cost result collapses back to cash-only. Set it high and the model makes the trade-off visible rather than pretending the minutes are free. The chart draws both cash and total-cost bars so the flip is inspectable instead of buried in a sentence.
Frequency scaling and the investing extension
Weekly meal frequency is converted to monthly with weeklyMeals * 52 / 12 and to yearly with weeklyMeals * 52. The page multiplies the per-meal money difference, not the time difference, to produce monthly and yearly money projections:
const mealsPerMonth = computed(() =>
safeNumber(state.weeklyMeals) * 52 / 12
);
const monthlyMoneyDiff = computed(() =>
moneyDiff.value * mealsPerMonth.value
);
const yearlyMoneyDiff = computed(() =>
moneyDiff.value * (safeNumber(state.weeklyMeals) * 52)
);
const monthlyMoneyDiffAbs = computed(() =>
Math.abs(monthlyMoneyDiff.value)
);
The absolute values make the magnitude readable, while the companion label says which side saves money. For the investing extension, the component treats the absolute monthly money difference as the amount set aside. It does not pretend that time savings can be deposited:
const positiveMonthlySavings = computed(() =>
Math.max(0, Math.abs(monthlyMoneyDiff.value))
);
const investFutureValue = computed(() => {
const months = Math.max(1,
Math.round(safeNumber(state.investYears) * 12));
const monthlyRate =
safeNumber(state.annualReturnRate) / 100 / 12;
const pmt = positiveMonthlySavings.value;
if (monthlyRate === 0) return pmt * months;
return pmt * (((1 + monthlyRate) ** months - 1) /
monthlyRate);
});
At zero return this is simply contribution times months. At a positive rate it is an ordinary end-of-period contribution formula. The “absolute” detail matters: even if delivery is cheaper, the extension displays the size of the difference as a hypothetical saving rather than passing a negative payment into the formula. It assumes the difference is actually invested every month.
A model cannot decide what cooking feels like
The calculator assumes every listed minute has the same time value and that a weekly frequency stays constant. It does not price leftovers, food waste, dishwashing fatigue, delivery quality, minimum order thresholds, subscriptions, grocery trips shared with other meals, or the enjoyment of cooking. A batch-cooking session may make the per-meal shopping time much lower than the form suggests; conversely, a single complicated recipe may make it much higher.
One practical way to use the model is to run two inputs for the same meal: a rushed weekday version and a relaxed weekend version. Keep the ingredient cost fixed, change the time fields, and see whether the winner flips. That preserves the source's intent better than averaging all evenings into one fictional meal, because the opportunity cost is usually situational. The displayed totals are rounded for currency, while the underlying computed values retain the minute-to-hour conversion.
The compound-interest extension is an assumption, not investment advice, and the return rate is not a forecast. The useful output is the sensitivity: try a lower time value, a realistic cleanup time, and a different frequency to see which assumptions change the answer. I turned these trade-offs into a small free tool: Delivery vs Cooking Cost Calculator.
Top comments (0)