← Back to blog Shopify OA 2026 Complete Guide|Backend / Full-Stack Real Questions, Cart State Machine & Webhook Idempotency
Shopify

Shopify OA 2026 Complete Guide|Backend / Full-Stack Real Questions, Cart State Machine & Webhook Idempotency

2026-05-16

After tightening University Recruiting in 2022, Shopify is reopening the New Grad and Early Career pipeline for the 2026 cycle — but the OA has shifted from "one LC Medium" to a business-driven, real-world coding test. This article uses 1point3acres, Reddit r/cscareerquestionsCAD, and Glassdoor data from 2026-03 to 2026-05 to lay out the platforms, question types, scoring rubric, and onsite handoff for Shopify OA.

Shopify OA at a Glance

Dimension Detail
Platform CodeSubmit (mainly Backend) / HackerRank (occasional Full-Stack)
Duration 90-120 minutes (Backend 90 / Full-Stack 120)
Questions 1-2 open-ended programming tasks (not LC-style)
Difficulty LC Medium algorithms + business modeling + test coverage
Pass Bar All hidden tests pass + code-review score ≥ 7/10
Feedback Window 5-14 days
Next Round Life Story behavioral + Technical pair programming

What makes Shopify different: the OA is not a pure algorithm test — it simulates real product work. Senior Engineers grade your submission like a PR review: readability, error handling, and test organization all count. One-line lambda golfing won't earn points; it loses them.

Question 1: Cart Inventory State Machine

Problem

Implement a minimal Cart class that supports:

Inventory and prices are injected into the constructor: Cart(inventory: dict, prices: dict).

Approach

This is not an algorithm problem; it's a software-engineering problem. Shopify's grading criteria:

  1. Clear state machine: cart has "pending / committing / done" states. Don't deduct inventory until checkout.
  2. Error codes vs exceptions: the spec wants string returns — don't raise.
  3. Atomicity: a failing checkout must roll back. The clean pattern is validate first, commit second, not "deduct then refund".
  4. Testability: inventory and prices are injected via DI so tests can mock them.

Python Solution

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):
        # Validate fully before committing — avoid partial deductions
        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

Time complexity: O(k) per operation, where k = number of SKUs in the cart Scoring tip: the two-phase checkout (validate + commit) is almost always a guaranteed point. A "deduct then refund" approach loses 2+ code-review points even if hidden tests pass.

Question 2: GraphQL Webhook Idempotency

Problem

Shopify pushes order webhooks (POST) to merchants, but the same event can be retried 2-5 times. Each payload contains:

{ "event_id": "evt_abc", "order_id": 1001, "amount": 250.0, "ts": 1715000000 }

Implement WebhookHandler:

Approach

Classic "idempotency + sliding window":

Python Solution

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)

Time complexity: amortized O(1) per handle (each event_id is pushed/popped at most once) Pitfall: _evict must use the current payload's ts, not time.time(). Hidden tests deliberately send out-of-order timestamps to catch this.

Question 3: Product Tag Search (prefix + fuzzy)

Problem

Implement a product search service. Stores register product tags (e.g. ["organic", "vegan", "gluten-free"]) and the API supports:

Approach

Python Solution

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]))]

Time complexity: index O(L), search O(L + R), where L = tag length and R = matched products

Prep Strategy

Priority Topic Recommended LeetCode / Resources
⭐⭐⭐ Design / state machines LC 146, LC 460, LC 1396, LC 355
⭐⭐⭐ Trie / prefix tree LC 208, LC 211, LC 642
⭐⭐ Sliding window / deque LC 239, LC 1438, LC 1004
⭐⭐ OOP / test organization Practice: 80%+ coverage on a mini-cart
GraphQL basics Shopify dev docs - GraphQL Admin API

Time budgeting: 90 min = 5 read + 10 class design + 35 first pass + 20 tests + 20 debug. Tests are not optional — Shopify's rubric explicitly includes test coverage.


FAQ

Q1: Is Shopify OA on CodeSubmit or HackerRank?

For 2026 Backend roles, mainly CodeSubmit — you download a starter repo, write locally, and git push to submit. Full-Stack and Front-End occasionally use HackerRank, but less than 20% of cases. CodeSubmit gives no hidden-test progress bar; results take 5-14 days.

Q2: With no hidden-test bar, how do I know my CodeSubmit solution is correct?

Write your own unit tests. CodeSubmit READMEs typically expect npm test or pytest to pass. Reviewers grade your test coverage first — this is the Shopify engineering culture. Cover at minimum: happy path, edge cases (empty input), error paths (invalid ID), and concurrency / ordering (when applicable).

Q3: Shopify's backend is Ruby on Rails — must I use Ruby for the OA?

No. CodeSubmit repos usually offer Ruby, Python, JS, Go, and Java starters. Pick the language you know best. Reviewers grade code organization, not language idioms. That said, if your résumé claims Ruby expertise but you submit in Go, be ready to explain at the onsite.

Q4: How long after passing Shopify OA does onsite happen?

Typically 2-3 weeks to a Recruiter Call → Life Story behavioral (45 min) → Technical pair programming (60 min) → Hiring Manager. Life Story is the Shopify signature round — you walk through your entire career, with the interviewer drilling into "why did you leave / join / pivot" at each turn. Prep at least 3 clear inflection-point narratives.

Q5: What's the difference between Shopify's CA and US 2026 Early Career roles?

CA roles (Ottawa / Toronto / Remote-CA) have more headcount and remote is fine. US roles are scarce, and remote-US applicants are often rejected unless they already have work authorization. Comp: New Grad CAD ~$100-115K + RSU; US (after currency adjustment) is moderately higher.

Q6: I failed Shopify OA — how soon can I reapply?

12-month cooldown. Before re-applying, prepare an engineering reflection note: list what you think you missed (tests, error handling, naming) on attempt one. Recruiters occasionally ask you to talk through this — Shopify cares about growth orientation.


Contact

If you're prepping a business-oriented OA like Shopify, Square, or Stripe, the bottleneck is rarely algorithms — it's code organization and PR-grade testing. We've curated 2025-2026 Shopify CodeSubmit real questions (with runnable multi-language starters and the grading rubric) — feel free to reach out.

Add WeChat Coding0201, get the Shopify OA bank and code-organization template.