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.
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:
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_idhash 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?