Shopify 自从 2022 大幅收紧 University Recruiting 后,2026 cycle 又把校招与 Early Career 通道重新开放——但 OA 也从过去的"一道 LC Medium"变成了一套业务导向极强的实战题。本文基于一亩三分地、Reddit r/cscareerquestionsCAD 与 Glassdoor 上 2026-03 到 2026-05 的最新反馈,把 Shopify OA 的平台、题型、评分逻辑、Onsite 衔接讲清楚。
Shopify OA 概览
| 维度 | 详情 |
|---|---|
| 平台 | CodeSubmit(Backend 主流) / HackerRank(Full-Stack 偶发) |
| 时长 | 90-120 分钟(Backend 90 / Full-Stack 120) |
| 题量 | 1-2 道开放式编程题(不是 LC 风格) |
| 难度 | LC Medium 算法 + 业务建模 + 测试覆盖 |
| 通过线 | 全部 hidden test 过 + 代码 review 评分 大于 7/10 |
| 反馈周期 | 5-14 天 |
| 二面 | Life Story 行为面 + Technical Pair Programming |
Shopify 独特之处:OA 不是纯算法题,而是模拟真实业务——你交付的代码会被 Senior Engineer 像 PR review 一样打分:可读性、错误处理、测试组织都计分。Lambda 一行流不会加分,反而扣分。
真题一:Cart Inventory State Machine(购物车库存状态机)
题目描述
实现一个最小化 Cart 类,支持以下方法:
add_item(product_id, qty):加入商品;若该 product 库存不足返回"OUT_OF_STOCK",否则返回当前购物车总价remove_item(product_id, qty):移除商品;若购物车不存在该 product 返回"NOT_FOUND"apply_discount(code):应用折扣码;折扣码与购物车状态有关——总价 ≥ $100 才能用SAVE10checkout():原子地扣减库存并清空购物车;若任一商品在 checkout 时已不足,全部回滚并返回"INVENTORY_CHANGED"
库存与单价以构造函数注入:Cart(inventory: dict, prices: dict)。
解题思路
这道题不是算法题,是软件工程题。Shopify 评分维度:
- 状态机清晰:购物车有"待结算 / 结算中 / 已完成"三态,checkout 之前不要扣库存
- 错误码 vs 异常:题目要求返回字符串 → 不要 raise
- 原子性:checkout 失败必须回滚——干净的写法是先校验再提交,不是"先扣再补"
- 可测性:库存与价格通过 DI 注入,方便 mock
Python 解法
from collections import defaultdict
class Cart:
def __init__(self, inventory: dict, prices: dict):
self.inventory = dict(inventory)
self.prices = dict(prices)
self.items = defaultdict(int)
self.discount = 0.0
def _subtotal(self) -> float:
return sum(self.prices[p] * q for p, q in self.items.items())
def add_item(self, product_id, qty):
if self.inventory.get(product_id, 0) < self.items[product_id] + qty:
return "OUT_OF_STOCK"
self.items[product_id] += qty
return round(self._subtotal() * (1 - self.discount), 2)
def remove_item(self, product_id, qty):
if product_id not in self.items:
return "NOT_FOUND"
self.items[product_id] -= qty
if self.items[product_id] <= 0:
del self.items[product_id]
return round(self._subtotal() * (1 - self.discount), 2)
def apply_discount(self, code):
if code == "SAVE10" and self._subtotal() >= 100:
self.discount = 0.10
return "OK"
return "INVALID"
def checkout(self):
# 先全量校验,再提交——避免部分扣减
for p, q in self.items.items():
if self.inventory.get(p, 0) < q:
return "INVENTORY_CHANGED"
for p, q in self.items.items():
self.inventory[p] -= q
total = round(self._subtotal() * (1 - self.discount), 2)
self.items.clear()
self.discount = 0.0
return total
时间复杂度:每次操作 O(k),k = 购物车 SKU 数
评分关键:checkout 的两阶段(验证 + 提交)几乎是必拿分点。先扣后补的解法即使 hidden test 全过,code review 分也会被扣 2 分以上。
真题二:GraphQL Webhook Idempotency(订单 webhook 去重)
题目描述
Shopify 给商户推送订单 webhook(POST 请求),但同一事件可能因为重试重复发送 2-5 次。每个 webhook payload 包含:
{ "event_id": "evt_abc", "order_id": 1001, "amount": 250.0, "ts": 1715000000 }
实现 WebhookHandler:
handle(payload) -> str:第一次处理某event_id返回"PROCESSED",重复返回"DUPLICATE"revenue_total() -> float:累计真实订单金额(去重后)- 额外要求:内存只允许保留最近 24 小时的 event_id,超过的可以被遗忘
解题思路
经典"幂等性 + 滑动窗口"组合:
- 用
set存活跃 event_id;用deque按时间窗滑动淘汰 revenue_total必须只统计未重复过的 event- 不要用
dict[event_id] = ts然后每次扫整个 dict 清理——O(n) per call 会在大数据量 hidden test 超时
Python 解法
from collections import deque
WINDOW = 24 * 60 * 60 # 24 hours in seconds
class WebhookHandler:
def __init__(self):
self.seen = set()
self.queue = deque() # (ts, event_id)
self.total = 0.0
def _evict(self, now):
while self.queue and self.queue[0][0] < now - WINDOW:
_, eid = self.queue.popleft()
self.seen.discard(eid)
def handle(self, payload):
eid = payload["event_id"]
ts = payload["ts"]
self._evict(ts)
if eid in self.seen:
return "DUPLICATE"
self.seen.add(eid)
self.queue.append((ts, eid))
self.total += payload["amount"]
return "PROCESSED"
def revenue_total(self):
return round(self.total, 2)
时间复杂度:handle 均摊 O(1)(每个 event_id 最多被 push/pop 一次)
陷阱:_evict 一定要用当前 payload 的 ts,而不是 time.time()——hidden test 会发送乱序时间戳来检查这一点。
真题三:Product Tag Search(前缀 + 模糊匹配)
题目描述
实现一个产品检索服务,store 端会注册大量商品标签(如 ["organic", "vegan", "gluten-free"]),并支持:
index(product_id, tags: list[str])search(query: str) -> list[int]:返回所有标签包含 query 作为前缀的 product_id(按 product_id 升序)top_tags(k: int) -> list[str]:返回出现次数 top-k 的 tag(出现次数相同按字典序)
解题思路
- 前缀检索 → Trie(不要直接 substring 遍历,hidden test 10w 商品会 TLE)
- top-k →
collections.Counter+heapq.nsmallest
Python 解法
from collections import Counter, defaultdict
import heapq
class TagSearch:
def __init__(self):
self.children = defaultdict(dict)
self.tag_to_products = defaultdict(set)
self.tag_count = Counter()
def _insert(self, tag, pid):
node = self.children
for ch in tag:
if ch not in node:
node[ch] = {}
node = node[ch]
node.setdefault("$products", set()).add(pid)
def _collect(self, node, out):
if "$products" in node:
out.update(node["$products"])
for k, v in node.items():
if k != "$products":
self._collect(v, out)
def index(self, pid, tags):
for t in tags:
self.tag_count[t] += 1
self.tag_to_products[t].add(pid)
self._insert(t, pid)
def search(self, query):
node = self.children
for ch in query:
if ch not in node:
return []
node = node[ch]
result = set()
self._collect(node, result)
return sorted(result)
def top_tags(self, k):
return [t for t, _ in heapq.nsmallest(
k, self.tag_count.items(), key=lambda x: (-x[1], x[0]))]
时间复杂度:index O(L),search O(L + R),L = 标签长度,R = 命中商品数
备考策略
| 优先级 | 重点 | 推荐题号 / 资源 |
|---|---|---|
| ⭐⭐⭐ | 设计 / 状态机 | LC 146、LC 460、LC 1396、LC 355 |
| ⭐⭐⭐ | Trie / 前缀树 | LC 208、LC 211、LC 642 |
| ⭐⭐ | 滑动窗口 / 双端队列 | LC 239、LC 1438、LC 1004 |
| ⭐⭐ | OOP / 测试组织 | 自练:写测试覆盖率 ≥ 80% 的 mini-cart |
| ⭐ | GraphQL 基础 | Shopify dev docs - GraphQL Admin API |
时间分配:90 分钟 = 读题 5 + 类设计 10 + 写第一版 35 + 写测试 20 + debug 20。写测试不是可选项——Shopify 的评分明确包含"test coverage"。
FAQ
Q1:Shopify OA 平台到底是 CodeSubmit 还是 HackerRank?
2026 Backend 岗主要走 CodeSubmit——这是一个让你下载 repo、本地写、git push 提交的平台,更像真实项目。Full-Stack 与 Front-End 偶尔走 HackerRank,但比例不到 20%。CodeSubmit 没有 hidden test 进度条,提交后 5-14 天才出结果。
Q2:Shopify OA 没有 hidden test 进度条,怎么知道自己写对了?
写自己的单元测试。CodeSubmit 提交后的 README 通常会要求 npm test 或 pytest 能跑通。评分官会先看你的测试覆盖率——这是 Shopify 工程文化的体现。建议至少覆盖:normal path、edge case (空输入)、error path (无效 ID)、并发或时序(如适用)。
Q3:Shopify 后端用 Ruby on Rails,OA 必须用 Ruby 吗?
不需要。CodeSubmit 仓库通常给出 Ruby / Python / JS / Go / Java 多个 starter,选你最熟的就行。Senior Engineer review 的是代码组织而不是语言惯用法。不过如果你的简历上写"精通 Ruby",又选其他语言,建议 Onsite 时准备解释一下。
Q4:Shopify OA 通过后多久 onsite?
通常 2-3 周进入 Recruiter Call → Life Story 行为面(45 min)→ Technical Pair Programming(60 min)→ Hiring Manager。Life Story 是 Shopify 的招牌环节,从你的整个职业生涯顺着讲一遍,每段经历追问"为什么离开 / 加入 / 转向"——准备至少 3 个职业转折点的清晰叙事。
Q5:Shopify 2026 Early Career 在加拿大和美国岗位有什么区别?
加拿大 (Ottawa / Toronto / Remote-CA) 名额较多,远程 OK。美国岗近两年招聘极少,远程美国常被拒签——除非已有 work authorization。薪资:CAD New Grad 约 100-115K + RSU,US 等级折算后实际略高。
Q6:Shopify OA 第一次没过,多久能重投?
12 个月窗口期。建议在第二次投递前主动准备一份"工程改进笔记":列出第一次 OA 你认为没做好的点(测试、错误处理、命名)。Shopify recruiter 偶尔会让你解释这段反思——他们更看重"成长性"。
联系方式
正在准备 Shopify、Square、Stripe 这类业务导向 OA 的 candidate,最缺的不是算法题,而是怎么组织代码 + 写出 PR 级别的测试。我们整理了 2025-2026 cycle 的 Shopify CodeSubmit 真题包(含完整可运行的多语言 starter + 评分标尺),欢迎联系交流。
立即添加微信 Coding0201,获取 Shopify OA 真题与代码组织模板。
- Email:[email protected]
- Telegram:@OAVOProxy