Donut Daily Production
2025 — A simulation of how many donuts a bakery should bake each day
01 Problem
A bakery bakes its donuts every morning and sells them by the dozen. Baking too few turns customers away, and baking too many leaves donuts that have to be sold off cheaply at closing.
The goal is the number of dozens to bake each day that earns the most over 5 selling days, rounded to a multiple of 5 dozen as the assignment requires.
02 Customers
The bakery gets 8, 10, 12, or 14 customers a day, most often 8, and 10.2 on average.
cust_per_day_counts <- c(8, 10, 12, 14)
cust_per_day_probs <- c(0.35, 0.30, 0.25, 0.10)
sample_customers <- function() {
sample(cust_per_day_counts, size = 1, prob = cust_per_day_probs)
}03 Orders
Each customer buys 1 dozen on 40% of visits, 2 on 30%, 3 on 20%, and 4 on 10%, or 2 dozen on average.
Adding up the orders of a day's customers gives that day's demand . It can be anywhere from 8 to 56 dozen, but averages 20.4 and rarely passes 35.
Order sizes do not depend on how many customers come, so the average demand is the average number of customers times the average order. The chance of each demand can be worked out exactly too. For each number of customers , the chance that orders add up to dozen is built one order at a time, then weighted by the chance of customers.
dozen_order_per_cust <- c(1, 2, 3, 4)
dozen_order_per_cust_probs <- c(0.40, 0.30, 0.20, 0.10)
sample_orders <- function(n) {
sample(dozen_order_per_cust, size = n, replace = TRUE, prob = dozen_order_per_cust_probs)
}04 Money
A dozen sells for Rp80,000 and costs Rp55,000 to bake, paid whether it sells or not. Whatever is left at closing goes to small shops nearby for Rp40,000 a dozen.
So every dozen that sells earns Rp25,000, and every dozen left over loses Rp15,000.
Baking dozen against a demand of sells and leaves , which is zero once demand reaches the batch. Every dozen baked either sells or is left over. So the day's profit is Rp25,000 for every dozen baked, minus Rp40,000 for every dozen left over, because a leftover dozen both misses the Rp25,000 it could have earned and loses the Rp15,000 it cost.
simulate_day <- function(dozen_count,
price = 80000,
cost = 55000,
salvage = 40000) {
n_cust <- sample_customers()
demand <- sum(sample_orders(n_cust))
sold <- min(demand, dozen_count)
leftover <- max(dozen_count - demand, 0)
revenue <- price * sold + salvage * leftover
profit <- revenue - cost * dozen_count
data.frame(
demand_dozens = demand,
sold_dozens = sold,
leftover_dozens = leftover,
profit = profit
)
}05 Simulation
A trial bakes the same number of dozens for 5 days and adds up the profit. Every level from 5 to 100 dozen, in steps of 5, gets 100 trials, the original app's defaults.
The level with the highest mean profit is recommended, and a tie goes to the smaller batch.
evaluate_dozens <- function(dozen_count, days = 5, trials = 200) {
profits <- numeric(trials)
left_avg <- numeric(trials)
for (r in 1:trials) {
df <- simulate_days(dozen_count, days)
profits[r] <- sum(df$profit)
left_avg[r] <- mean(df$leftover_dozens)
}
data.frame(
dozen_count = dozen_count,
mean_profit = mean(profits),
mean_avg_leftover = mean(left_avg)
)
}
dozens_grid <- seq(5, input$max_dozens, by = 5)
res <- do.call(rbind, lapply(dozens_grid, function(dozens) {
evaluate_dozens(dozens, days = input$days, trials = input$trials)
}))
res[order(-res$mean_profit, res$dozen_count), ][1, , drop = FALSE]06 Answer
Baking 20 dozen a day earns the most, Rp2,135,200 over 5 days on average, with 1.8 dozen left over a day. Past 40 dozen almost nothing extra sells, so every 5 more dozen a day lose Rp375,000 over 5 days.
The expected profit of every batch size can also be added up exactly from the demand distribution. Only the leftover in is random, so the expected profit needs just the expected leftover, and since every day is alike, 5 days earn 5 times as much as one.
Zoomed in on 15 to 30 dozen, the exact curve peaks at 22 dozen, and among multiples of 5 it gives Rp2,127,214 for 20 dozen and Rp2,083,875 for 25, only Rp43,339 apart.
In this run the simulated means sit up to Rp14,075 off the exact curve. With 100 trials, the standard error of the gap between the 20 and 25 dozen means is Rp43,355, about the size of the margin itself, so this run picks the right level but a rerun can pick 25.
07 Margin
The exact answer has a simple reason. Baking dozen number on top of the first changes only that one dozen. It sells when demand reaches , earning Rp25,000, and is left over otherwise, losing Rp15,000, so it is worth baking as long as it sells on more than 37.5% of days. This is the newsvendor model, the textbook problem of how much to stock of something that cannot be sold the next day.
Each extra dozen sells no more often than the one before it, so the profit climbs while the chance it sells, , stays above 0.375 and falls after. Dozen number 22 sells on 39.0% of days and dozen number 23 on only 32.6%, so the best batch of any size is 22 dozen.
Among multiples of 5, five more dozen are worth baking when they sell on more than 37.5% of days on average. Dozens 16 to 20 sell on 67.8% of days on average and dozens 21 to 25 on only 33.2%, so 20 is the best multiple of 5.
08 Original app
The simulation was first built in R as an interactive Shiny dashboard. Sliders set the largest batch to test, up to 200 dozen, and the trials per level, from 50 to 1,000, and inputs change the days, price, cost, and salvage price. Each run draws the profit curve, marks the recommended level, and lists the averages for every level in a table.
It is still live on shinyapps.io. The free tier puts the app to sleep when nobody is using it, so the first visit can take a while to load.

09 Limits
Rounding to a multiple of 5 dozen has a price. Baking 22 dozen would earn Rp2,146,843 over 5 days, Rp19,629 more than 20.
With 100 trials per level, and fresh random days for every level, the noise in each mean is larger than the gap between 20 and 25 dozen. Rerunning the whole app 2,000 times recommends 25 dozen 14.4% of the time. More trials, or the same random days for every level, would settle it.
The 5-day horizon does not change the answer. Leftovers do not carry over, so every day is the same decision and the 5-day profit is 5 times the daily one. The value box labeled mean 5-day leftover in fact shows the mean leftover per day.
The probabilities come from the assignment, not from sales records, and every day of the week is assumed alike. Real sales logs from the bakery would make the answer worth trusting.
Donut Daily Production
Bakes 20 dozen a day for Rp2,135,200 over 5 days