Back to Shopify questions
CodingSoftware Engineer

OOP Pair Programming

Role: Software Engineer


Shopify's technical round is a collaborative OOP design problem. You implement a multi-step system with your interviewer, AI tools allowed. The problem grows in complexity with each step, and a scalability discussion follows.

Format

  • 30–45 minutes of implementation
  • AI tools (Cursor, Copilot) explicitly encouraged — but you must understand and explain any code generated
  • The interviewer pair-programs with you — communicate decisions, don't just code silently
  • Ends with a discussion: "How would this scale to 1B+ objects?"

Example Problem: Bank Account System

This pattern (or a variation like car rental, word guessing game, or a CRUD app) is what Shopify typically gives.

text
Step 1: Implement a BankAccount class with deposit and withdraw

Step 2: Add transaction history — track every deposit/withdrawal with a timestamp

Step 3: Add a transfer method between two accounts. Ensure atomicity — if the sender has insufficient funds, neither account changes.

Step 4 (if time): Add interest accrual — a method that applies a given interest rate to the balance.

Implementation:

python
from datetime import datetime
from dataclasses import dataclass, field


@dataclass
class Transaction:
    kind: str        # "deposit" | "withdraw" | "transfer_in" | "transfer_out"
    amount: float
    timestamp: datetime = field(default_factory=datetime.now)
    note: str = ""


class InsufficientFundsError(Exception):
    pass


class BankAccount:
    def __init__(self, owner: str, initial_balance: float = 0.0):
        self.owner = owner
        self._balance = initial_balance
        self._history: list[Transaction] = []

    @property
    def balance(self) -> float:
        return self._balance

    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self._balance += amount
        self._history.append(Transaction("deposit", amount))

    def withdraw(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if amount > self._balance:
            raise InsufficientFundsError(f"Insufficient funds: balance is {self._balance}")
        self._balance -= amount
        self._history.append(Transaction("withdraw", amount))

    def transfer(self, target: "BankAccount", amount: float) -> None:
        if amount > self._balance:
            raise InsufficientFundsError(f"Insufficient funds for transfer")
        # Deduct first, then credit — keeps atomicity in single-threaded context
        self._balance -= amount
        self._history.append(Transaction("transfer_out", amount, note=f"to {target.owner}"))
        target._balance += amount
        target._history.append(Transaction("transfer_in", amount, note=f"from {self.owner}"))

    def apply_interest(self, rate: float) -> None:
        interest = self._balance * rate
        self._balance += interest
        self._history.append(Transaction("deposit", interest, note="interest"))

    def statement(self) -> list[Transaction]:
        return list(self._history)

Scalability Discussion

After implementation, expect: "How would this scale to handle 1 billion accounts?"

Key points to hit:

  • Persistence: Move from in-memory to a database (Postgres for accounts, append-only ledger table for transactions)
  • Sharding: Partition accounts by account_id hash across multiple DB nodes
  • Concurrency: Pessimistic locking (SELECT FOR UPDATE) or optimistic locking (version field) to handle concurrent transfers safely
  • Transaction history: Store in a separate time-series or append-only table, indexed by (account_id, timestamp). Don't store in memory.
  • Caching: Cache account balances in Redis with write-through or write-back policy for hot accounts

Other Reported Variants

Car Rental System: Car, Customer, Booking classes. Add availability checking, date-based pricing, and cancellation logic.

Word Guessing Game (Wordle-style): Game class with a secret word. guess(word) returns per-letter feedback (correct, present, absent). Add attempts tracking and win/loss detection.

CRUD App: A simple in-memory store for a given entity (e.g., products, orders) with create/read/update/delete operations and basic validation.

Follow-ups

  • How do you handle concurrent transfers between the same two accounts (deadlock)?
  • How would you implement rollback if one side of a transfer fails in a distributed system?
  • How would you paginate a transaction history of millions of records efficiently?