Amazon SDE 面试经验 — Behavioral + Restaurant Ordering System OOD
本次面试包含两道 Behavioral Questions 和一轮 Restaurant Ordering System 的 OOD/Coding 设计题。BQ 主要考察 Ownership 与 Deliver Results,OOD 则围绕 Pizza 点餐与动态定价系统展开,重点涉及实体抽象、Composition、Inheritance、Polymorphism、SOLID 以及系统扩展性。
📌 Interview Overview
- Interview Type: Behavioral + OOD/Coding
- Interview Topics: Ownership、Deliver Results、OOD、OOP、Domain Modeling、Composition、Inheritance、Polymorphism、SOLID、Open-Closed Principle、Pricing Engine、Code Quality
- Coding Difficulty: 3.5/5
- Candidate Background: 曾在 TotalEnergies 从事 Data Analyst 相关工作,并在 CMU 参与 RiskBot 等项目。
- 本次材料没有提供明确的面试日期。
- 原始材料中的面试官级别、最终结果以及部分公司层面的判断属于复盘分析,不作为 Amazon 官方信息呈现。
👤 Candidate Background
候选人此前在 TotalEnergies 参与 Supply Chain Reports 相关工作。
在工作过程中,候选人发现 Data Pipeline 存在安全控制方面的问题。虽然安全并不属于其主要职责范围,但候选人主动研究 Security Practices,整理风险与解决方案,并将相关内容形成结构化 Memo 汇报给管理层。
这段经历最终促使候选人进一步关注 Cybersecurity,并进入 CMU 攻读相关 Master's。
此外,候选人在 RiskBot Capstone 中参与 Voice-based AI System 的开发。在项目距离最终展示仅剩两周时,候选人承担了额外的 Coding 和 Integration 工作,并重新尝试新的 Architecture,以解决 Voice Integration 不稳定的问题。
🧠 Behavioral Questions
BQ 1 — Ownership
Interviewer Question:
"Can you tell me about a time when you took something significant outside of your area of responsibility? Why was it important? And what's the outcome?"
Answer:
Situation
在 TotalEnergies 工作期间,候选人在构建 Supply Chain Reports 时发现 Data Pipeline 缺少必要的 Security Controls。
Task
虽然 Security 并不属于候选人的 Data Analyst 职责范围,但候选人认为这个问题可能影响数据安全,因此决定主动处理这一问题。
Action
- 主动研究相关 Security Practices。
- 梳理 Data Pipeline 中存在的具体安全风险。
- 制作结构化 Memo,记录风险以及对应的解决方案。
- 将分析结果提交给 Management。
Result
团队最终实施了 Authentication 和 Permissions 等安全控制。
这段经历也让候选人进一步意识到自己对 Cybersecurity 的兴趣,并促使其之后选择在 CMU 攻读 Cybersecurity Master's。
BQ 2 — Deliver Results Under Tight Deadline
Interviewer Question:
"Give me another example of a time you were able to deliver an important project under tight deadline. What sacrifices did you make to meet that deadline? How didn't it impact the final deliverable? What was the outcome?"
Answer:
Situation
RiskBot Capstone 距离最终展示只剩两周,但项目中的 Voice Integration 仍然不稳定。
Task
候选人需要在剩余时间内完成一个更加完整、稳定的 Voice-based AI System,并确保最终 Presentation 能够顺利进行。
Action
- 放弃部分 Spring Break 个人时间。
- 重新 Prototype 一套 Architecture。
- 主动承担额外 Coding 工作。
- 承担重新进行 Integration 所带来的风险。
Result
最终完成了 Voice-based AI System。
根据候选人的描述,新的系统拥有更加 Seamless 的 Voice Interaction,并且 Architecture Clarity 获得了认可。
Sacrifices
- Personal Time
- Additional Responsibilities
- 重新进行 Architecture 和 Integration 所带来的风险
Impact
候选人认为这些牺牲并没有降低最终 Deliverable 的质量,反而帮助项目获得了更好的最终效果。
💻 Interview Process
OOD — Restaurant Ordering System
Problem:
材料中的复盘描述,本轮 Coding/OOD 题目围绕一个 Restaurant Ordering System 展开。
系统需要支持不同类型的 Menu Item,其中 Pizza 是核心实体之一。
一个 Pizza 可以由以下组成部分构成:
- Base Crust
- Size
- Toppings
系统需要实现一个 Price Calculation Engine,根据 Pizza 的不同属性计算最终价格。
材料中给出的基础计价公式为:
Price = (base_price + sum(toppings_prices)) * size_multiplier
Example:
Thin Crust = $8
Cheese = $2
Small Multiplier = 0.75
因此:
Small Thin Crust Cheese Pizza
= ($8 + $2) * 0.75
= $7.50
对于 Medium:
Medium Thin Crust Cheese Pizza
= ($8 + $2) * 1.0
= $10.00
Follow-up 1 — Size Extensibility
题目随后进一步要求系统能够支持动态新增 Size。
例如:
Gigantic = 4.0
Personal = 0.5
新增 Size 时不应该修改原有 Pricing Logic,也不能通过大量 Hard-coded if-else 来完成。
这一阶段的核心是考察系统是否具有良好的 Extensibility。
Follow-up 2 — Objectification of Dimensions
面试官进一步推动候选人将原本的基本数据类型抽象成独立对象。
材料中记录了类似:
"The size itself can be a class right?"
的引导。
随后 Size 不再只是一个 String 或 Float,而应该成为拥有自身属性和行为的实体。
例如:
Size
- name
- multiplier
类似地,PizzaType、Topping 等概念也可以进行独立建模。
Follow-up 3 — Entity Modeling
面试官继续追问 Pizza 与 Topping、PizzaType 等实体之间的关系。
设计逐渐从简单的数据结构转向 Domain Modeling。
核心思路是将现实业务中的重要名词映射为独立 Domain Entity,例如:
Size
Crust
Topping
Pizza
MenuItem
Order
这样可以避免所有业务逻辑集中在一个大型 Class 或 Function 中。
Follow-up 4 — MenuItem Abstraction
随着系统需要支持 Pizza 之外的其他菜品,材料中的设计进一步引入:
MenuItem
作为抽象基类。
Pizza 和其他类型的菜品可以继承 MenuItem,并实现统一的:
calculate_price()
接口。
这样 Order 就不需要知道具体商品类型,而可以通过 Polymorphism 统一计算不同 Menu Item 的价格。
Follow-up 5 — Composition Over Inheritance
材料中的复盘进一步将设计归纳为 Composition。
Pizza 不需要为每一种属性组合创建独立 Subclass。
例如,不应该出现:
SmallThinCheesePizza
LargeThinCheesePizza
SmallCheesyCrustPizza
而应该将:
Size
Crust
Topping
作为独立实体组合到 Pizza 中。
这种设计可以减少 Class Explosion,并使不同属性能够自由组合。
Follow-up 6 — Pricing Extension
材料进一步讨论了如何将 Pricing Logic 与具体 Menu Item 解耦。
如果未来需要支持:
- Promotion
- Discount
- Tax
- Dynamic Menu Configuration
可以考虑通过 Strategy Pattern、Decorator Pattern 等方式进一步拆分 Pricing Logic。
需要注意,这部分属于材料中的设计扩展分析,并非全部被明确记录为本次面试实际发生的 Follow-up。
🏗️ System Design
本题主要属于 OOD/OOP,而不是传统的 Distributed System Design。
材料中重点讨论的是如何通过 Domain Modeling 和 Object-Oriented Design 构建一个具有扩展性的 Restaurant Ordering System。
核心领域模型可以抽象为:
MenuItem
├── Pizza
└── Pasta
Pizza
├── Crust
├── Size
└── Toppings
Order
└── MenuItems
Size
Size
- name
- multiplier
Size 封装尺寸名称和价格倍率。
Topping
Topping
- name
- price
Topping 负责保存配料本身的信息和价格。
Crust
Crust
- name
- price
Crust 负责保存底胚类型及其基础价格。
MenuItem
MenuItem
- name
- base_price
- calculate_price()
通过统一接口支持不同类型菜品的 Polymorphic Pricing。
Pizza
Pizza
- crust
- size
- toppings
Pizza 通过 Composition 将不同 Domain Entity 组合起来。
Order
Order
- items
Order 负责维护多个 MenuItem,并统一计算订单总价。
💡 Improved OOD Implementation
材料中给出的重构方向可以整理为以下 Python 实现:
from abc import ABC, abstractmethod
from decimal import Decimal, ROUND_HALF_UP
from typing import List, Optional
class Size:
def __init__(self, name: str, multiplier: Decimal):
if multiplier <= Decimal("0"):
raise ValueError("Multiplier must be strictly positive")
self.name = name
self.multiplier = multiplier
class Topping:
def __init__(self, name: str, price: Decimal):
if price < Decimal("0"):
raise ValueError("Price cannot be negative")
self.name = name
self.price = price
class Crust:
def __init__(self, name: str, price: Decimal):
if price < Decimal("0"):
raise ValueError("Price cannot be negative")
self.name = name
self.price = price
class MenuItem(ABC):
def __init__(self, name: str, base_price: Decimal):
self.name = name
self.base_price = base_price
@abstractmethod
def calculate_price(self) -> Decimal:
pass
class Pizza(MenuItem):
def __init__(
self,
name: str,
crust: Crust,
size: Size,
toppings: Optional[List[Topping]] = None
):
super().__init__(name, crust.price)
self.crust = crust
self.size = size
self.toppings = toppings if toppings is not None else []
def add_topping(self, topping: Topping) -> None:
self.toppings.append(topping)
def calculate_price(self) -> Decimal:
topping_total = sum(
(t.price for t in self.toppings),
Decimal("0.00")
)
subtotal = self.crust.price + topping_total
final_price = subtotal * self.size.multiplier
return final_price.quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)
class Pasta(MenuItem):
def __init__(
self,
name: str,
base_price: Decimal,
sauce_price: Decimal
):
super().__init__(name, base_price)
self.sauce_price = sauce_price
def calculate_price(self) -> Decimal:
return (
self.base_price + self.sauce_price
).quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)
class Order:
def __init__(self):
self.items: List[MenuItem] = []
def add_item(self, item: MenuItem) -> None:
self.items.append(item)
def calculate_total(self) -> Decimal:
return sum(
(item.calculate_price() for item in self.items),
Decimal("0.00")
)
Complexity
- 菜品构建与参数注入:O(1)
- 单个 Pizza 价格计算:O(K),K 为 Toppings 数量
- Order 总价计算:O(N × K),N 为订单中的菜品数量
- Domain Entity 的单个对象空间开销:O(1)
- 整个 Order 的空间复杂度取决于实际创建的 MenuItem、Topping 和配置对象数量
🗣️ English & Communication
材料中的复盘认为,候选人在面试官连续提示后能够快速调整设计,并逐步完成从基本类型到 Domain Entity、再到抽象基类的设计演进。
如果遇到类似 OOD 题,可以使用以下表达主动控制设计方向:
解释为什么 Size 应该成为 Class:
"To ensure extensibility and adhere to the Open-Closed Principle, I will encapsulate Size and Topping as standalone domain models rather than primitive types."
解释为什么使用 Composition:
"Instead of deeply nested inheritance hierarchies, I prefer composing Pizza with Crust, Size, and a collection of Toppings. This prevents class explosion and makes runtime customization seamless."
解释 MenuItem 和 Polymorphism:
"By introducing an abstract MenuItem base class with a standardized calculate_price interface, the order aggregate can evaluate mixed baskets containing Pizzas and Pastas polymorphically."
❌ What Went Wrong
根据材料中的复盘,候选人的主要问题集中在 OOD 的第一反应。
1. 初始设计偏向 Primitive Types
候选人一开始倾向于使用:
String
Float
Dictionary / Map
快速完成价格计算。
这种方式可以较快实现基础功能,但面对新增 Size、Pizza Type 或其他业务属性时,扩展性较差。
2. 没有第一时间进行 Domain Modeling
材料中的复盘认为,候选人没有在一开始就主动将:
Size
Crust
Topping
Pizza
识别为潜在 Domain Entity。
而是在面试官多次提示后才逐步完成抽象。
3. 缺少对 Class Explosion 的主动考虑
如果每一种 Pizza 组合都使用独立的 Class,会导致大量 Subclass。
因此更合理的方式是使用 Composition,让 Pizza 在运行时组合不同的 Size、Crust 和 Topping。
4. 金额计算的工程细节
材料中的复盘指出,涉及金额计算时直接使用 Float/Double 可能产生精度问题。
更稳妥的工程实现可以使用 Decimal 或整数 Cents。
这一点属于材料中的工程化分析,而不是明确记录的面试官原话。
📚 Preparation
材料中没有提供候选人本人明确描述的具体备考计划,因此不额外补充候选人的 Preparation 内容。
💡 My Takeaways
1. OOD 题不要先急着写代码
看到:
Restaurant
Ordering
Menu
Pizza
Pricing
这类业务题时,可以先提取 Domain Nouns。
例如:
Size
Crust
Topping
Pizza
MenuItem
Order
然后再确定每个 Entity 的职责。
2. 看到可能变化的属性,要考虑封装
Size 不应该永远只是:
"small"
"medium"
"large"
如果未来可能出现:
Personal
Large
Gigantic
那么 Size 本身就是一个具有业务含义的 Domain Object。
3. Composition 通常比组合爆炸的继承更灵活
不要为每一种 Pizza 组合创建一个 Class。
应该让:
Pizza = Crust + Size + Toppings
通过组合实现不同配置。
4. 用抽象基类统一行为
如果系统未来存在:
Pizza
Pasta
Beverage
可以通过:
MenuItem
提供统一接口:
calculate_price()
Order 只需要处理 MenuItem,而不需要针对每种具体商品写大量条件分支。
5. 先展示设计意图,再开始实现
如果面试官希望考察 OOD,开局可以先花几十秒说明:
"I see Size, Crust, and Topping as separate domain entities because these dimensions are likely to evolve independently."
然后再开始写代码。
这样可以让面试官快速看到你对 Extensibility 和 Domain Modeling 的理解。
General Advice
这类 OOD 面试可以形成一个固定检查框架:
Step 1: 提取业务领域中的核心名词
Step 2: 判断哪些名词应该成为独立 Entity
Step 3: 明确每个 Entity 的属性和职责
Step 4: 判断哪些关系适合 Composition
Step 5: 判断是否需要 Abstract Base Class
Step 6: 定义统一接口
Step 7: 检查是否存在 Class Explosion
Step 8: 考虑未来新增类型和业务规则
Step 9: 最后再讨论 Design Pattern 和工程细节
对于 Pricing System,还应该主动注意金额精度、参数校验以及未来 Promotion / Discount 等规则的扩展方式。
📝 Related Practice Topics
材料中推荐的相关 OOD 练习包括:
- Design Parking Lot
- Design Elevator System
- Design BlackJack / Card Game
- Design Shopping Cart & Promotion Engine
这些属于材料中的备考推荐,并不代表本次面试实际出现的题目。
