InterviewLegend

Arrays, Strings & Hashing

Alex Chen24 min readUpdated 15 real interview reports
In this lesson
  • Recognize arrays, strings, and hashing as the substrate the classic Shopify coding screen leans on most
  • Reach for prefix-sum plus a hashmap, frequency counting, and binary-search boundaries on commerce-shaped inputs
  • Model merchant data cleanly (ledgers, order lines, shipping tiers) so the interviewer can layer complexity
  • Work three reported shapes end to end, each with a tested escalation

Alongside the pair-programming build Shopify is known for, most loops carry a more classic coding round. Expect a live shared editor, your own language, and a medium-difficulty problem you talk through and test as you go. That round is where array, string, and hashing fundamentals surface most directly, and it is the family this lesson covers. Nothing here is exotic. The work is reading merchant-shaped input carefully, picking the one data structure that makes the rule cheap, and keeping your code clean enough that the interviewer wants to add a second requirement on top.

Expect the problem to sound like a feature ticket. Shopify is the commerce platform behind millions of independent stores, so its screens dress the same fundamentals in its own domain. Think an inventory ledger, a run of order line items, or a table of shipping rates by weight. Strip the story down to the pattern fast. A cart is a hashmap. A stock ledger is a prefix sum. Best-sellers is frequency counting plus a sort. Then re-attach the domain in your variable names, because a candidate who writes units_sold and shipping_band is already framing the work around merchants.

How the screen is framed

The bar Shopify states in its own interview guidance is craft over speed: a tested, cleanly approached solution scores higher than a rushed one, even if you never reach the final case. So the screen is not a race to spot an O(n) trick. It is a check that you can model the input into a clean structure, apply the stated rules exactly, and prove correctness with a few of your own assertions before the interviewer asks. Docs and search are usually allowed. A pre-written solution to the exact problem is not.

The Shopify angle
The commerce framing is a signal. The interviewer wants to see you reach past the story to the structure underneath, then keep the story in your naming. Say the prompt is "find the longest run of stock adjustments that leaves the count where it started." Name the pattern, prefix sums with a hashmap, then write on_hand and ledger instead of arr and sum. That is the naming of someone who has debugged a real inventory bug, which is exactly why Shopify phrases the problem in stock and ledger terms in the first place.

Three tools carry almost every problem in this family. A hashmap of first-seen prefix sums turns "longest window that nets to X" from an O(n squared) scan into one pass. A hashmap of counts turns ranking and deduplication into a tally plus a sort. A binary search on a sorted array turns "which tier does this value fall into" into a boundary search. Learn to hear which one a rule is asking for, and the rest is careful code.

The prefix-sum plus hashmap pattern

The most reported fundamentals problem in this family is the longest subarray that sums to zero. Learn it as a template, because the same shape answers a whole cluster of "longest window that nets to a target" questions. The idea is that a running prefix sum encodes the whole history in one number. If the same prefix value shows up at two positions, every element between them cancels out to zero. So you record the earliest index where each prefix value appeared, and whenever you see that value again, the span between them is a candidate answer.

python
def longest_zero_sum_window(deltas):
seen = {0: -1} # prefix sum -> earliest index it appeared (seed the empty prefix)
running = 0
best = 0
for i, delta in enumerate(deltas):
running += delta
if running in seen:
best = max(best, i - seen[running]) # same prefix twice => the gap nets to zero
else:
seen[running] = i # keep only the FIRST index, for the longest span
return best

Two details decide whether this works. Seeding the map with {0: -1} handles a window that starts at index zero, because the empty prefix has sum zero at position minus one. And you must store only the first index a prefix value appears at, never overwrite it, because the earliest position gives the widest span. Overwriting is the most common bug, and it quietly returns a shorter window that still passes the easy tests.

The same map answers a different question with one change. To COUNT the windows that net to a target, store how many times each prefix sum has occurred instead of its index. At each position, add the count of running - target. Problem one below works that escalation in full.

Asked at Shopify

Three reported shapes, one per tool. Each starts as a clean fundamentals problem and carries the kind of escalation a Shopify interviewer layers on once the first version passes. Try each in the editor, then reveal the walkthrough.

1

Longest Zero-Sum Window: Prefix Sums

MediumCommonly asked
Problem
An inventory ledger for one product is a list of stock adjustments in the order they happened. A positive number is a restock, a negative number is a sale or a write-off. You want the longest stretch of consecutive adjustments after which the on-hand count is exactly what it was before that stretch began, in other words the longest contiguous run that nets to zero.

Return the LENGTH of the longest such run, or 0 if no non-empty run nets to zero.

Input: deltas = [1, 2, -2, 4, -4]

Output: 4

The run [2, -2, 4, -4] nets to zero and has length 4. The whole array nets to 1, so it does not count.

Input: deltas = [4, -4, 3, -1, -2]

Output: 5

The entire array nets to zero: 4 - 4 + 3 - 1 - 2 = 0, so the answer is its full length 5.

Input: deltas = [5]

Output: 0

A single non-zero adjustment can never net to zero, so there is no qualifying run.

How this uses the pattern

Track a running prefix sum. Seed a map with {0: -1} and record the first index each prefix value appears at. When a prefix value repeats, the elements between the two positions net to zero, so the span is a candidate answer.

Longest Zero-Sum Window: Prefix Sums

Loading...
Click "Run" to execute your code

Test Cases

Input: [[1, 2, -2, 4, -4]]Expected: 4
Input: [[4, -4, 3, -1, -2]]Expected: 5
Input: [[5]]Expected: 0
Input: [[]]Expected: 0
Input: [[0]]Expected: 1
Input: [[2, -2]]Expected: 2
The Shopify angle
Shopify frames this as an inventory or ledger question rather than a bare array puzzle, and the framing is the tell: they want you to name the prefix-sum insight, not brute-force every window. The reward is the seeded {0: -1} map and holding only the first index of each prefix, which is exactly the edge the interviewer probes. Write a test where the qualifying run starts at index zero, because that is the case a missing seed silently breaks.
Take it further
The common escalation keeps the ledger but changes the question to counting: how many runs net to a specific target change, say the stock ended exactly k units higher. That flips the map from first-seen index to a frequency count of prefix sums, and the answer accumulates as you sweep.
2

Top Best-Sellers: Count and Rank

MediumFrequently asked
Problem
A day of orders arrives as a list of line-item strings, each formatted sku:quantity, where sku is a product code and quantity is a positive integer number of units on that line. The same sku can appear on many lines.

Return the top n best-selling skus by total units sold, most units first. Break ties between skus with equal units by sku in ascending (alphabetical) order, so the output is deterministic. If fewer than n distinct skus exist, return all of them.

Input: lines = ["shirt:2", "mug:5", "shirt:3", "hat:5", "mug:1"] n = 2

Output: ["mug", "hat"]

Totals are mug 6, shirt 5, hat 5. mug leads. shirt and hat tie at 5, so hat wins the tiebreak alphabetically, giving ["mug", "hat"].

Input: lines = ["shirt:2", "mug:5", "shirt:3", "hat:5", "mug:1"] n = 1

Output: ["mug"]

mug has the most units at 6, so the single best-seller is mug.

How this uses the pattern

Tally first, rank second. Parse each line into (sku, quantity) and sum quantities per sku in a hashmap. Then sort the skus by units descending, using the sku itself ascending as the tiebreaker, and take the first n.

Top Best-Sellers: Count and Rank

Loading...
Click "Run" to execute your code

Test Cases

Input: ["shirt:2", "mug:5", "shirt:3", "hat:5", "mug:1"], 2Expected: ["mug", "hat"]
Input: ["shirt:2", "mug:5", "shirt:3", "hat:5", "mug:1"], 1Expected: ["mug"]
Input: ["shirt:2", "mug:5", "shirt:3", "hat:5", "mug:1"], 9Expected: ["mug", "hat", "shirt"]
Input: [], 3Expected: []
Input: ["a:1", "b:1"], 2Expected: ["a", "b"]
The Shopify angle
This product-ranking shape shows up in Shopify screens, and the tiebreaker is where it bites. An interviewer feeds you two products with equal sales to see whether your ordering is deterministic or a coin flip. A reviewer would flag nondeterministic output in a pull request, so the fluent answer is one explicit sort key, (-units, sku), never a bare sort by count. Parse defensively too, because a stray space around the colon is exactly the input they slip in.
Take it further
The natural escalation is scale: the catalog has millions of skus but n is small, so sorting the entire tally is wasteful. Bucketing skus by their unit count lets you walk from the highest count down and stop as soon as you have collected n, without ever sorting the whole list.
3

Shipping-Band Lookup: Binary Search

MediumCommonly asked
Problem
A store charges shipping in weight bands. You are given thresholds, a strictly increasing list of the minimum order weight for each band, and rates, the shipping cost in cents for each band, aligned by index. An order falls into the highest band whose threshold is less than or equal to its weight.

Given an order weight, return the shipping cost that applies. If the weight is below the first threshold, no band applies, so return None.

Input: thresholds = [0, 5, 20, 50] rates = [300, 800, 1500, 3000] weight = 12

Output: 800

Bands with a threshold at or below 12 are the ones at 0 and 5. The highest is index 1 (threshold 5), so the rate is 800.

Input: thresholds = [0, 5, 20, 50] rates = [300, 800, 1500, 3000] weight = 50

Output: 3000

The weight 50 exactly meets the top threshold, so it lands in the last band at rate 3000.

How this uses the pattern

Binary search for a boundary. Find the highest index whose threshold is less than or equal to the weight. Track the best qualifying index and move right when a band qualifies, left when it does not.

Shipping-Band Lookup: Binary Search

Loading...
Click "Run" to execute your code

Test Cases

Input: thresholds=[0,5,20,50], rates=[300,800,1500,3000], weight=12Expected: 800
Input: thresholds=[0,5,20,50], rates=[300,800,1500,3000], weight=0Expected: 300
Input: thresholds=[0,5,20,50], rates=[300,800,1500,3000], weight=49Expected: 1500
Input: thresholds=[0,5,20,50], rates=[300,800,1500,3000], weight=50Expected: 3000
Input: thresholds=[5,20,50], rates=[800,1500,3000], weight=3Expected: None
The Shopify angle
Shopify wraps binary search in a shipping or pricing tier because that is a real lookup a merchant hits on every order. The interviewer watches the boundary handling: does an exact threshold match land in the right band, and does an underweight order return None instead of crashing. Anchoring on the last index that qualified, rather than searching for an exact value, is what makes those edges fall out cleanly. A linear scan also works, but the sorted thresholds make the binary search the answer they are looking for.
Take it further
A frequent twist flips the direction. Find the smallest band whose threshold can contain the weight, meaning the leftmost threshold greater than or equal to it. Same search, mirrored condition.
4

Order Total: Stack Discounts by Precedence, Then Tax by Region

MediumFrequently asked
Problem
A checkout request arrives as a list of line items, each an object with a sku, a unit_cents price, and a qty. Alongside it come a list of discount rules, the buyer's region code, and a tax_rates map from region code to a tax rate in basis points, so 1300 means 13 percent. Compute the order total in integer cents.

Each rule is an object with a scope of "item" or "order", a kind of "percent" or "flat", and an integer value. An item rule also names the sku it applies to and touches only lines with that sku. A rule naming a sku that is not in the order does nothing.

Apply the rules in this precedence:

- Item rules first, line by line. Within a line, every percent rule applies before any flat rule, each kind in the order the rules were given. A percent rule takes floor(amount * value / 100) cents off the running line amount, so two percent rules on one line compound, and 20 then 10 leaves 72 percent of the line. A flat item rule takes value cents off every unit on the line.
- Then sum the lines into a subtotal and apply the order rules the same way, percent first and flat second, with a flat order rule taking value cents off the subtotal once.
- A line amount or the subtotal never drops below zero.
- Finally add tax of floor(subtotal * rate / 10000) using the rate for region. A region with no entry in the map pays no tax.

Return the discounted subtotal plus tax as an integer number of cents.

Input: lines = [{"sku": "shirt", "unit_cents": 2500, "qty": 2}, {"sku": "mug", "unit_cents": 900, "qty": 3}] rules = [{"scope": "order", "kind": "flat", "value": 500}, {"scope": "item", "sku": "mug", "kind": "percent", "value": 20}] region = "ON" tax_rates = {"ON": 1300, "BC": 1200}

Output: 7525

Shirts total 5000 with no item rule. Mugs start at 2700 and the 20 percent rule takes 540 off, leaving 2160. The subtotal 7160 then loses the flat 500 for 6660, even though that rule was listed first. Ontario tax is floor(6660 * 1300 / 10000) = 865, so the total is 7525.

Input: lines = [{"sku": "mug", "unit_cents": 1000, "qty": 1}] rules = [{"scope": "item", "sku": "mug", "kind": "percent", "value": 20}, {"scope": "item", "sku": "mug", "kind": "percent", "value": 10}] region = "ZZ" tax_rates = {"ON": 1300}

Output: 720

The first rule takes 200 off for 800, then the second takes floor(800 * 10 / 100) = 80 off for 720. The two rules compound to 28 percent off, where adding them would have given 30. Region ZZ has no rate, so no tax is added.

Input: lines = [{"sku": "mug", "unit_cents": 900, "qty": 1}] rules = [{"scope": "order", "kind": "flat", "value": 2000}] region = "ON" tax_rates = {"ON": 1300}

Output: 0

The flat 2000 exceeds the 900 subtotal, which floors at zero. Tax on zero is zero, so the order total is 0.

How this uses the pattern

Treat the rules as data and run the same two-pass routine at each scope. Filter to the rules that apply: item rules by sku per line, then the order rules. Apply every percent rule, then every flat rule with a floor at zero, and only then look up the region rate. Integer cents and floor division throughout.

Order Total: Stack Discounts by Precedence, Then Tax by Region

Loading...
Click "Run" to execute your code

Test Cases

Input: [[{"sku": "shirt", "unit_cents": 2500, "qty": 2}, {"sku": "mug", "unit_cents": 900, "qty": 3}], [{"scope": "order", "kind": "flat", "value": 500}, {"scope": "item", "sku": "mug", "kind": "percent", "value": 20}], "ON", {"ON": 1300, "BC": 1200}]Expected: 7525
Input: [[{"sku": "mug", "unit_cents": 1000, "qty": 1}], [{"scope": "item", "sku": "mug", "kind": "percent", "value": 20}, {"scope": "item", "sku": "mug", "kind": "percent", "value": 10}], "ZZ", {"ON": 1300}]Expected: 720
Input: [[{"sku": "mug", "unit_cents": 900, "qty": 1}], [{"scope": "order", "kind": "flat", "value": 2000}], "ON", {"ON": 1300}]Expected: 0
Input: [[{"sku": "mug", "unit_cents": 1000, "qty": 1}], [{"scope": "order", "kind": "flat", "value": 500}, {"scope": "order", "kind": "percent", "value": 20}], "ON", {"ON": 1300}]Expected: 339
Input: [[{"sku": "shirt", "unit_cents": 2500, "qty": 2}, {"sku": "mug", "unit_cents": 900, "qty": 3}], [{"scope": "order", "kind": "percent", "value": 10}, {"scope": "item", "sku": "mug", "kind": "flat", "value": 100}], "BC", {"ON": 1300, "BC": 1200}]Expected: 7459
Input: [[{"sku": "mug", "unit_cents": 900, "qty": 1}], [{"scope": "item", "sku": "hat", "kind": "percent", "value": 50}], "ON", {"ON": 1300}]Expected: 1017
Input: [[], [{"scope": "order", "kind": "flat", "value": 500}], "ON", {"ON": 1300}]Expected: 0
Input: [[{"sku": "pen", "unit_cents": 333, "qty": 3}], [{"scope": "item", "sku": "pen", "kind": "percent", "value": 15}], "NY", {"NY": 8875}]Expected: 1604
Input: [[{"sku": "mug", "unit_cents": 200, "qty": 3}, {"sku": "hat", "unit_cents": 1000, "qty": 1}], [{"scope": "item", "sku": "mug", "kind": "flat", "value": 300}], "ZZ", {"ON": 1300}]Expected: 1000
The Shopify angle
Discount stacking is a live concern at Shopify, where a merchant runs a product-level sale and an order-level promo code at once and the checkout applies product discounts before order discounts. Keep the rules as data and write one apply function that runs per scope, so precedence lives in two loops. Hold every amount in integer cents with floor division. The panel's favorite probes, a flat rule bigger than the line and two percent rules on one sku, then fall out of the floor and the loop order. The cart build in the hash maps lesson owns add and set_quantity; here you take lines in and hand a total out, the way a pricing service sits beside a cart service.
Take it further
The usual Part 2 asks for the breakdown a merchant sees on the order page: the discount allocated to each line, the discount taken at the order level, the tax, and the total. Because every rule already computes an explicit discount amount before subtracting it, you record those amounts as you go and return a receipt object in place of one integer. A later escalation adds a promo code that unlocks an order rule only when the discounted subtotal clears a minimum spend. It slots in as a filter on the order rules before the existing passes run.
5

Two Sum: One Pass with a Complement Map

EasySometimes asked
Problem
A shopper wants to spend a gift card down to zero. You are given prices, the price in cents of each line item in their cart in cart order, and balance, the remaining value on the card in cents. Find two different line items whose prices add up to balance exactly.

Return the two indices as a list [i, j] with i < j. If no such pair of line items exists, return an empty list. A line item cannot be paired with itself, and you may assume at most one qualifying pair exists.

Input: prices = [1500, 2500, 800, 1200] balance = 2000

Output: [2, 3]

The items at indices 2 and 3 cost 800 + 1200 = 2000, which uses the whole balance. No other pair reaches 2000.

Input: prices = [1000, 250, 750] balance = 1000

Output: [1, 2]

Index 0 alone equals the balance, but one item is not a pair. Indices 1 and 2 cost 250 + 750 = 1000.

Input: prices = [500] balance = 1000

Output: []

A single 500-cent item would need a second 500-cent item, and there is none. It cannot pair with itself.

How this uses the pattern

Walk the prices once with a map from price to index. For each item compute the complement, balance minus price, and check the map for it before inserting the current item. Checking before inserting means an item never matches itself.

Two Sum: One Pass with a Complement Map

Loading...
Click "Run" to execute your code

Test Cases

Input: [[1500, 2500, 800, 1200], 2000]Expected: [2, 3]
Input: [[1500, 2500, 800, 1200], 4000]Expected: [0, 1]
Input: [[1000, 250, 750], 1000]Expected: [1, 2]
Input: [[500], 1000]Expected: []
Input: [[500, 500], 1000]Expected: [0, 1]
Input: [[300, 700, 400], 2000]Expected: []
Input: [[], 100]Expected: []
Input: [[0, 0], 0]Expected: [0, 1]
The Shopify angle
Two-sum-style hashing is part of the substrate Shopify's technical screen keeps returning to, and it tends to arrive in cart or ledger terms. Treat it as the opening check that you reach for a map before a double loop, because the harder ledger questions on the same screen build on that reflex. The gift-card dressing carries two edges to say out loud as you code. The order of lookup and insert decides whether an item can pair with itself, and two identical prices are a legitimate pair that a set-based approach quietly drops. Write the test for each before the interviewer asks.
Take it further
The escalation usually drops the "at most one pair" promise. First the interviewer asks for every pair of line items that hits the balance, where duplicate prices make the count interesting. Then the balance becomes a target for three items. Sort the prices and sweep a two-pointer window inside a loop over the first item, which generalizes to the k-sum family without any new ideas.
6

Order Fraud Flags: Aggregate per Key and Threshold

MediumSometimes asked
Problem
A day of orders for one store arrives as a list of strings, each formatted order_id,card,address,timestamp,amount_cents. card is the fingerprint of the payment card, address is the shipping address, timestamp is a non-negative integer number of seconds, and amount_cents is the order total in cents. Order ids are unique and no field contains a comma.

Flag orders with three rules, each with its own threshold.

address-spread: a card that ships to more than max_addresses distinct addresses across the batch. Every order on that card is flagged.

velocity: time is split into fixed buckets of window_seconds starting at zero, so an order's bucket is timestamp // window_seconds. When a card has more than max_per_window orders in one bucket, every order in that bucket is flagged.

amount-cap: any single order whose amount_cents is greater than max_amount_cents.

Return a list of [order_id, rule] pairs, one for every rule an order trips, ordered by the order's position in the input and then by rule in the order given above.

Input: lines = ["o1,card_a,addr_1,0,4500", "o2,card_a,addr_2,20,1200", "o3,card_a,addr_3,40,800", "o4,card_b,addr_9,100,72000", "o5,card_c,addr_5,200,3000"] max_addresses = 2, max_per_window = 3, window_seconds = 60, max_amount_cents = 50000

Output: [["o1", "address-spread"], ["o2", "address-spread"], ["o3", "address-spread"], ["o4", "amount-cap"]]

card_a ships to three distinct addresses, one more than allowed, so all three of its orders are flagged. Its three orders in bucket 0 do not exceed the limit of three. o4 is 72000 cents, above the 50000 cap. o5 trips nothing.

Input: lines = ["o1,card_v,addr_1,0,1000", "o2,card_v,addr_1,10,1000", "o3,card_v,addr_1,20,1000", "o4,card_v,addr_1,30,1000", "o5,card_v,addr_1,70,1000"] max_addresses = 2, max_per_window = 3, window_seconds = 60, max_amount_cents = 50000

Output: [["o1", "velocity"], ["o2", "velocity"], ["o3", "velocity"], ["o4", "velocity"]]

Bucket 0 covers timestamps 0 through 59 and holds four orders on card_v, one over the limit, so all four are flagged. o5 at 70 sits alone in bucket 1. One address and small amounts, so no other rule fires.

How this uses the pattern

Parse each line into an order record once. Then every rule is the same move: group orders by a key (the card, or the card plus a time bucket), measure the group, compare to its threshold, and flag. Keep each rule in its own function that returns a set of flagged ids, and merge at the end.

Order Fraud Flags: Aggregate per Key and Threshold

Loading...
Click "Run" to execute your code

Test Cases

Input: [["o1,card_a,addr_1,0,4500", "o2,card_a,addr_2,20,1200", "o3,card_a,addr_3,40,800", "o4,card_b,addr_9,100,72000", "o5,card_c,addr_5,200,3000"], 2, 3, 60, 50000]Expected: [["o1", "address-spread"], ["o2", "address-spread"], ["o3", "address-spread"], ["o4", "amount-cap"]]
Input: [["o1,card_v,addr_1,0,1000", "o2,card_v,addr_1,10,1000", "o3,card_v,addr_1,20,1000", "o4,card_v,addr_1,30,1000", "o5,card_v,addr_1,70,1000"], 2, 3, 60, 50000]Expected: [["o1", "velocity"], ["o2", "velocity"], ["o3", "velocity"], ["o4", "velocity"]]
Input: [["o1,card_x,addr_1,40,100", "o2,card_x,addr_1,50,100", "o3,card_x,addr_1,59,100", "o4,card_x,addr_1,60,100", "o5,card_x,addr_1,61,100"], 2, 3, 60, 50000]Expected: []
Input: [["o1,card_u,addr_1,130,100", "o2,card_u,addr_1,5,100", "o3,card_u,addr_1,125,100", "o4,card_u,addr_1,70,100", "o5,card_u,addr_1,100,100", "o6,card_u,addr_1,140,100", "o7,card_u,addr_1,170,100"], 2, 3, 60, 50000]Expected: [["o1", "velocity"], ["o3", "velocity"], ["o6", "velocity"], ["o7", "velocity"]]
Input: [["o1,card_m,addr_1,0,100", "o2,card_m,addr_2,5,100", "o3,card_m,addr_3,10,99999"], 2, 3, 60, 50000]Expected: [["o1", "address-spread"], ["o2", "address-spread"], ["o3", "address-spread"], ["o3", "amount-cap"]]
Input: [[], 2, 3, 60, 50000]Expected: []
Input: [["o1,card_e,addr_1,0,50000", "o2,card_e,addr_2,100,50000", "o3,card_e,addr_1,200,50000"], 2, 3, 60, 50000]Expected: []
Input: [["o1,card_p,addr_1,0,500", "o2,card_p,addr_1,5,500", "o3,card_p,addr_2,100,1500"], 1, 1, 10, 1000]Expected: [["o1", "address-spread"], ["o1", "velocity"], ["o2", "address-spread"], ["o2", "velocity"], ["o3", "address-spread"], ["o3", "amount-cap"]]
Input: [["o1,card_a,addr_1,0,100", "o2,card_b,addr_1,0,100", "o3,card_a,addr_2,1,100", "o4,card_b,addr_2,1,100", "o5,card_c,addr_3,2,100", "o6,card_a,addr_3,3,100"], 2, 3, 60, 50000]Expected: [["o1", "address-spread"], ["o3", "address-spread"], ["o6", "address-spread"]]
The Shopify angle
Open any order in a Shopify merchant's admin and a fraud analysis card lists indicators that read like a rule table. Among them: how many orders this card has placed, whether shipping and billing details line up, and whether the total sits out of line for the store. The heuristics in the prompt are deliberately simple, so the structure carries the weight. Write each rule as its own function that returns the ids it flags, then merge at the end. Thresholds get tuned per merchant on a checkout team, and a fourth rule should land without you touching the first three. Group on the card fingerprint, because a fraudster rotates addresses and emails long before the card changes.
Take it further
The layer most likely to land is the sliding window. Fixed buckets let a burst that straddles a boundary through: orders at 40, 50, 59 and 60 seconds sit in two different buckets and never trip the count. Sort each card's timestamps and keep a window of the last window_seconds, flagging the order that overflows it, and you have written the same loop a rate limiter uses.

Where the screen goes wrong

The ways candidates lose this round are rarely about missing the algorithm. They are about parsing sloppily, skipping the tests, or leaving output nondeterministic, and each maps straight onto what Shopify grades.

Do

  • Name the pattern before coding: prefix sum, count map, or boundary search.
  • Parse defensively, stripping spaces and converting quantities to int at the point you read them.
  • Give every sort an explicit compound key so ties resolve the same way every run.
  • Write and run a handful of assertions the moment the first version works, covering the empty and boundary cases.
  • Keep the domain in your naming, so on_hand, units_sold, and shipping_band read like real code.

Don't

  • Overwrite a first-seen prefix index and quietly return a shorter window.
  • Sort by count alone and let equal-count items land in dict-iteration order.
  • Reach for float on money or weight when integers keep the comparisons exact.
  • Skip the underweight or empty-input edge and wait for the interviewer to surface it.
  • Chase an O(n) micro-optimization before the code is correct and readable.

Hear the structure under the story, then keep the story in your names and your tests. A clean, tested tally or prefix-sum pass that you can extend when the interviewer adds a requirement beats a clever one-liner that drops the boundary case. That is the craft-over-speed bar Shopify states outright, and this family of problems is where it is easiest to show.

Key Takeaways

  • ✓The classic Shopify coding screen leans hardest on arrays, strings, and hashing, with the input handed to you as ledgers, order lines, and shipping tiers.
  • ✓Three tools carry the family: a first-seen prefix-sum map for zero-sum and target windows, a count map for ranking, and a boundary binary search for tiers.
  • ✓Model the input cleanly, apply the stated rule exactly, and make output deterministic with explicit sort keys. Craft and tests outrank speed here.
  • ✓Structure the first version so the interviewer can layer a requirement on top, then prove each step with assertions you write yourself.
Had your Shopify interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Shopify interview guide

Know what Shopify asks before you walk in.

26 coding · 5 system design · 21 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.