Requirements
Functional Requirements
1. Beverage Selection:
- Display available beverages
- Select beverage type
2. Customization:
- Size (Small, Medium, Large)
- Add-ons (Milk, Sugar, Cream, Flavor shots)
- Temperature (Hot, Iced)
3. Ingredient Management:
- Track ingredient levels
- Low ingredient alerts
4. Payment:
- Cash/Coin
- Card
- Mobile payment
5. Dispensing:
- Prepare beverage
- Add ingredients in correct order
- Dispense when complete
Core Entities
CoffeeMachine, Beverage, Ingredient,
Order, Payment, Size, AddOn
Beverage Types
├── Espresso
│ ├── Americano
│ ├── Latte
│ └── Cappuccino
├── Drip Coffee
├── Tea
│ ├── Green Tea
│ └── Black Tea
└── Hot Chocolate
Beverage Selection
Beverage Interface
public interface Beverage {
String getName();
double getBasePrice();
Map<Ingredient, Integer> getIngredients();
List<AddOn> getAvailableAddOns();
}
Concrete Beverages
public abstract class CoffeeBeverage implements Beverage {
protected final String name;
protected final double basePrice;
protected final Map<Ingredient, Integer> ingredients;
@Override
public Map<Ingredient, Integer> getIngredients() {
return new HashMap<>(ingredients);
}
}
public class Latte extends CoffeeBeverage {
public Latte() {
super("Latte", 4.50);
ingredients.put(Ingredient.ESPRESSO, 2);
ingredients.put(Ingredient.MILK, 3);
ingredients.put(Ingredient.MILK_FOAM, 1);
}
@Override
public List<AddOn> getAvailableAddOns() {
return List.of(AddOn.CARAMEL, AddOn.VANILLA,
AddOn.SOY_MILK, AddOn.OAT_MILK);
}
}
public class Americano extends CoffeeBeverage {
public Americano() {
super("Americano", 3.50);
ingredients.put(Ingredient.ESPRESSO, 2);
ingredients.put(Ingredient.WATER, 4);
}
}
Size and Pricing
public enum Size {
SMALL(0.8, 8),
MEDIUM(1.0, 12),
LARGE(1.3, 16);
private final double priceMultiplier;
private final int volumeOz;
}
public class PriceCalculator {
public double calculate(Beverage beverage, Size size, List<AddOn> addOns) {
double price = beverage.getBasePrice() * size.getPriceMultiplier();
price += addOns.stream()
.mapToDouble(AddOn::getPrice)
.sum();
return Math.round(price * 100.0) / 100.0;
}
}
Order Builder
public class OrderBuilder {
private Beverage beverage;
private Size size = Size.MEDIUM;
private final List<AddOn> addOns = new ArrayList<>();
public OrderBuilder beverage(Beverage beverage) {
this.beverage = beverage;
return this;
}
public OrderBuilder size(Size size) {
this.size = size;
return this;
}
public OrderBuilder addOn(AddOn addOn) {
addOns.add(addOn);
return this;
}
public Order build() {
double price = new PriceCalculator()
.calculate(beverage, size, addOns);
return new Order(beverage, size, addOns, price);
}
}
// Usage
Order order = new OrderBuilder()
.beverage(new Latte())
.size(Size.LARGE)
.addOn(AddOn.CARAMEL)
.addOn(AddOn.SOY_MILK)
.build();
Customization
AddOn System
public enum AddOn {
CARAMEL(0.50),
VANILLA(0.50),
MOCHA(0.50),
SOY_MILK(0.70),
OAT_MILK(0.70),
EXTRA_SHOT(0.75),
WHIPPED_CREAM(0.00),
CINNAMON(0.00);
private final double price;
}
Beverage Preparation
public class BeveragePreparer {
private final IngredientDispenser dispenser;
public void prepare(Order order) {
// 1. Get base ingredients
Map<Ingredient, Integer> ingredients =
order.getBeverage().getIngredients();
// 2. Scale by size
double multiplier = order.getSize().getPriceMultiplier();
// 3. Add extra ingredients from add-ons
for (AddOn addOn : order.getAddOns()) {
addIngredientsForAddOn(ingredients, addOn);
}
// 4. Dispense each ingredient
for (Map.Entry<Ingredient, Integer> entry : ingredients.entrySet()) {
int amount = (int) (entry.getValue() * multiplier);
dispenser.dispense(entry.getKey(), amount);
}
System.out.println("Beverage ready: " + order.getBeverage().getName());
}
private void addIngredientsForAddOn(
Map<Ingredient, Integer> ingredients, AddOn addOn) {
switch (addOn) {
case EXTRA_SHOT:
ingredients.merge(Ingredient.ESPRESSO, 1, Integer::sum);
break;
case SOY_MILK:
ingredients.put(Ingredient.MILK, 0); // Replace
ingredients.put(Ingredient.SOY_MILK, 3);
break;
}
}
}
Ingredient Inventory
public class IngredientInventory {
private final Map<Ingredient, Integer> stock;
private final int lowStockThreshold;
public boolean hasIngredients(Map<Ingredient, Integer> required) {
return required.entrySet().stream()
.allMatch(e -> stock.getOrDefault(e.getKey(), 0) >= e.getValue());
}
public void deduct(Map<Ingredient, Integer> used) {
used.forEach((ingredient, amount) -> {
stock.merge(ingredient, -amount, Integer::sum);
if (stock.get(ingredient) <= lowStockThreshold) {
notifyLowStock(ingredient);
}
});
}
private void notifyLowStock(Ingredient ingredient) {
// Observer: alert for restocking
}
}
Follow-ups
Follow-up Questions
1. How to handle multiple drinks in one order?
→ Queue of orders
→ Sequential preparation
→ Order status tracking
2. How to handle loyalty program?
→ Points per purchase
→ Free drink redemption
→ Member pricing
3. How to handle custom recipes?
→ Recipe builder interface
→ Save custom recipes
→ Share recipes
4. How to handle maintenance?
→ Self-cleaning cycle
→ Bean grinding calibration
→ Water filter replacement
5. How to handle mobile ordering?
→ API for remote ordering
→ Queue management
→ Ready notification
Design Patterns
| Pattern | Usage |
|---|---|
| Builder | Order construction |
| Strategy | Pricing calculation |
| Observer | Low stock alerts |
| Template Method | Beverage preparation |
| Factory | Beverage creation |
Machine Display
┌──────────────────────────────────────┐
│ COFFEE MACHINE │
├──────────────────────────────────────┤
│ Select Beverage: │
│ 1. Espresso $3.50 │
│ 2. Latte $4.50 │
│ 3. Cappuccino $4.50 │
│ 4. Americano $3.50 │
│ 5. Green Tea $3.00 │
├──────────────────────────────────────┤
│ Size: [S] [M] [L] │
│ Add-ons: [Extra Shot] [Soy Milk] │
│ Temperature: [Hot] [Iced] │
├──────────────────────────────────────┤
│ Total: $5.70 │
│ Insert payment │
└──────────────────────────────────────┘
Practice Problems
Design a scalable Coffee Machine system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Coffee Machine to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Coffee Machine and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What pattern is used for order construction?
2. How does size affect pricing?
3. How are add-ons handled in ingredient dispensing?
4. What triggers a low ingredient alert?
5. How does the Template Method pattern apply?
Flashcards
Question
What pattern builds coffee orders?
Click to reveal answer
Answer
Builder pattern. Step by step: select beverage → choose size → add-ons → build order.
Question
How is pricing calculated?
Click to reveal answer
Answer
Base price × size multiplier + add-on prices. Size: Small=0.8, Medium=1.0, Large=1.3.
Question
How are add-ons applied?
Click to reveal answer
Answer
Add-ons add extra ingredients (extra shot) or replace ingredients (soy milk replaces regular milk).
Question
Ingredient inventory management?
Click to reveal answer
Answer
Track stock levels. Check availability before preparation. Deduct after dispensing. Alert on low stock.
Question
Beverage preparation flow?
Click to reveal answer
Answer
Template: 1) Get base ingredients, 2) Scale by size, 3) Add customization, 4) Dispense each ingredient.
Revision Notes
Key Takeaways
- 1.Builder pattern constructs orders step by step with beverage, size, and add-ons
- 2.Size multiplier scales both price and ingredient amounts
- 3.Add-ons either add extra or replace existing ingredients
- 4.Ingredient inventory tracks stock and alerts on low levels
- 5.Template Method pattern standardizes beverage preparation flow
Interview Tips
- •Show Builder pattern for order construction
- •Explain how size affects both pricing and ingredients
- •Discuss ingredient dispensing with add-on handling
- •Mention mobile ordering as extension
Cheat Sheet
Coffee Machine - Cheat Sheet
Order Flow:
- Select beverage type
- Choose size (S/M/L)
- Add customizations
- Calculate price
- Process payment
- Prepare and dispense
Pricing:
Base Price × Size Multiplier + Add-ons
Ingredient Flow:
- Get base ingredients
- Scale by size
- Apply add-ons
- Dispense ingredients
- Deduct from inventory
Patterns:
Builder (orders), Strategy (pricing), Observer (stock alerts), Template Method (preparation)