← 返回博客列表 Shopify OA 2026 完整攻略|Backend / Full-Stack 真题、Cart 状态机与 Webhook 幂等性
Shopify

Shopify OA 2026 完整攻略|Backend / Full-Stack 真题、Cart 状态机与 Webhook 幂等性

2026-05-16

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 类,支持以下方法:

库存与单价以构造函数注入:Cart(inventory: dict, prices: dict)

解题思路

这道题不是算法题,是软件工程题。Shopify 评分维度:

  1. 状态机清晰:购物车有"待结算 / 结算中 / 已完成"三态,checkout 之前不要扣库存
  2. 错误码 vs 异常:题目要求返回字符串 → 不要 raise
  3. 原子性:checkout 失败必须回滚——干净的写法是先校验再提交,不是"先扣再补"
  4. 可测性:库存与价格通过 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

解题思路

经典"幂等性 + 滑动窗口"组合:

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"]),并支持:

解题思路

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 testpytest 能跑通。评分官会先看你的测试覆盖率——这是 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 真题与代码组织模板