Arrays, Strings & Hashing
- Recognize the array, string, and hashing problems that open the Instacart CodeSignal assessment and warm up the onsite coding rounds
- Drill three reusable skeletons: a hash-map tally, a sliding window over sorted events, and a complement lookup
- Parse a pile of records and pass the hidden tests on the first run
This is the warm-up band, and at Instacart it owns the front of the funnel. The online assessment runs on CodeSignal as four leveled tasks in about seventy minutes, and the first two levels live right here: a hash-map aggregation to start, then a sliding window over time-ordered events. The same shapes open the onsite coding rounds. Nothing on this band is exotic. The Instacart bar is whether your code parses a messy pile of records and passes the hidden tests, cleanly, while the clock runs.
Three skeletons cover almost everything in this band, and they all lean on the same structure. A hash map used as a tally, when you aggregate order records into a per-store number. A sliding window over sorted timestamps, when you ask how busy the busiest delivery stretch was. A hash map used as a lookup, when you pair two catalog items against a target. Grocery records go in, one summary comes out. Learn the three shapes cold and most of the early levels fall out in a few minutes each.
How Instacart frames it
Instacart wraps these in its own marketplace. A frequency count becomes the fill rate at a store, the fraction of ordered items a shopper actually found on the shelf. A sliding window becomes the peak hour a fulfillment team has to staff for. A two-sum becomes a customer trying to spend a promo credit on exactly two items. The three-sided marketplace is always in the frame somewhere, because every record ties back to a customer, a shopper, or a retailer. Your job is to hear the grocery story, name the skeleton under it, and start.
Then expect the problem to grow. The signature Instacart move is the staged follow-up: you ship a clean first version, and the interviewer adds a business rule to your own code rather than handing you a fresh question. A per-store tally gains a minimum-orders threshold so one lucky order cannot top the leaderboard. A window over sorted timestamps has to handle arrivals that stream in out of order. A pairing check turns into a running service that answers after every item added to the cart. This maps straight onto the CodeSignal leveled format, where each level modifies the last.
Every problem on this band is one of two uses of a hash map, sometimes wrapped in a sorted-order sweep. A map is either a tally or a lookup. In a tally the key is a group and the value is a running count or sum you fold records into. In a lookup you ask whether the piece you need has already gone by. The sliding window is the third shape, and it only works when the events are sorted, so both edges move forward and the running summary stays valid without re-scanning. Decide which of the three the record stream needs before you type, because that choice is the whole problem. The rest is bookkeeping.
The three templates to know
Three skeletons cover the band. Type each until it runs from memory, so in the room your attention goes to parsing the records and catching the follow-up while the machinery writes itself.
Pattern 1: The hash-map tally
Reach for a tally when the answer is a per-group summary of a record stream. The key is whatever you group by, a store id or an item, and the value is a running count or sum you fold each record into. Sweep once, then read the winner off the totals. Sorting the keys before you scan for the best gives you a clean tie-break for free.
from collections import defaultdictdef best_group(records): total = defaultdict(int) # group -> how many records hits = defaultdict(int) # group -> how many were a "hit" for group, was_hit in records: total[group] += 1 if was_hit: hits[group] += 1 best_group, best_rate = None, -1.0 for group in sorted(total): # ascending key settles ties toward the smaller id rate = hits[group] / total[group] if rate > best_rate: best_rate, best_group = rate, group return best_groupThe one subtlety is the tie-break. When two stores share the top rate, iterating the keys in sorted order and keeping a strict greater-than means the first one you meet wins, which is the smallest id. Change that single rule and the follow-up threshold, a minimum record count per group, becomes a one-line filter over the same two maps.
Pattern 2: Sliding window over sorted events
When events carry timestamps and the question asks about the busiest span, sort them if they are not already, then sweep two edges forward. The right edge takes in each new event. The left edge advances only far enough to keep the span within the allowed length. Every event enters once and leaves at most once, so the two edges together make a single linear pass.
def busiest_span(times, w): left = 0 best = 0 for right in range(len(times)): while times[right] - times[left] > w: # span too wide, drop the oldest left += 1 best = max(best, right - left + 1) # events now inside the window return bestThe shape never changes. What changes is what you measure inside the window: a count, a sum of items to pick, a distinct-customer set. The rule in the while also never changes for a fixed-length window. This template rests on the events being sorted, so the moment the follow-up says arrivals stream in out of order, a sort goes in first and the rest stands.
Pattern 3: The complement lookup
When you need two elements that satisfy a relation, walk the list once and ask the map whether the partner you need has already gone by. For a target sum, the partner of the current price is the target minus it. Storing each value against its earliest index as you pass gives you the pair in a single sweep with no nested loop.
def find_pair(values, target): seen = {} # value -> earliest index it appeared for j, value in enumerate(values): partner = target - value if partner in seen: return [seen[partner], j] # partner came first, so its index is smaller if value not in seen: seen[value] = j # keep the earliest index for duplicates return [-1, -1]Asked at Instacart
Three problems in the Instacart grocery frame, one per template. Each test case is the function's arguments as a JSON array and the expected return. Write the clean base in the editor and run it first, then read the walkthrough, which carries the staged follow-up the rest of the way.
Top Fill-Rate Store: Hash-Map Tally
Problem
records, a list of order-line rows. Each row is [store_id, item_id, found], where found is 1 if the shopper found the item on the shelf and 0 if it was out of stock.A store's fill rate is the number of found items divided by the total number of items ordered at that store. Return the
store_id with the highest fill rate. If two stores tie, return the smaller store_id.Input encoding. Each test case is a JSON array
[records], a single list argument. The function returns the store id as an integer.Input: records = [[1,10,1],[1,11,0],[2,20,1]]
Output: 2
Store 1 found 1 of 2 (0.5); store 2 found 1 of 1 (1.0). Store 2 wins.
Input: records = [[3,1,0],[7,2,0]]
Output: 3
Both stores have a 0.0 fill rate, so the smaller id, 3, breaks the tie.
Input: records = [[5,1,1],[5,2,1],[5,3,1]]
Output: 5
Only one store, and it found everything.
Fold the rows into two maps keyed by store: total items and found items. Then scan stores in ascending id order, keeping the highest rate, so the smallest id wins any tie for free.
Top Fill-Rate Store: Hash-Map Tally
Click "Run" to execute your code
Test Cases
[[[1,10,1],[1,11,0],[2,20,1]]]Expected: 2[[[5,1,1],[5,2,1],[5,3,1]]]Expected: 5[[[1,10,1],[1,11,1],[2,20,1],[2,21,0]]]Expected: 1[[[3,1,0],[7,2,0]]]Expected: 3[[[9,1,1]]]Expected: 9[[[2,1,1],[2,2,0],[1,3,1],[1,4,1]]]Expected: 1k stores by fill rate, ignoring any store with fewer than min_orders items. A store that found its one and only item should not top the leaderboard. The same two maps drive it; you filter, then sort. That version is worked through below.Busiest Delivery Window: Sliding Window
Problem
starts, a list of order start times in minutes, sorted ascending, and a window length w. A window is a time span of length w; an order at time t is covered by a window beginning at s when s <= t <= s + w.Return the largest number of orders that any single window of length
w can cover. This is the peak load a fulfillment team has to staff for.Input encoding. Each test case is a JSON array
[starts, w]. The function returns the count as an integer.Input: starts = [0,10,20,30,40], w = 20
Output: 3
A window starting at 0 covers 0, 10, and 20, which is three orders. No 20-minute span covers four.
Input: starts = [0,60,120], w = 60
Output: 2
A window starting at 0 covers 0 and 60, since 60 - 0 = 60 is within w.
Input: starts = [10,10,10,10], w = 0
Output: 4
All four orders start at the same minute, so a zero-length window covers every one.
Sweep two edges forward over the sorted times. The right edge adds each order; the left edge advances while the span from left to right exceeds w. The window size at each step is a candidate answer.
Busiest Delivery Window: Sliding Window
Click "Run" to execute your code
Test Cases
[[0,10,20,30,40], 20]Expected: 3[[0,60,120], 60]Expected: 2[[5], 100]Expected: 1[[0,1,2,3,4], 1]Expected: 2[[0,100,200,300], 50]Expected: 1[[10,10,10,10], 0]Expected: 4Immediate Delivery Percentage: First-Order Tally Per Customer
Problem
orders, a list of rows [order_id, customer_id, order_day, delivery_day], where the two days are integers counting days from a fixed epoch. An order is immediate when delivery_day equals order_day; any later day means the customer scheduled it.A customer's first order is the row with the smallest
order_day for that customer_id. When two rows share that day, the smaller order_id is the first. Return the percentage of customers whose first order was immediate, rounded to two decimal places. The rows arrive in no particular order, and orders has at least one row.Input encoding. Each test case is a JSON array
[orders], a single list argument. The function returns the percentage as a number, for example 50.0.Input: orders = [[1,1,1,2],[2,2,2,2],[3,1,11,12],[4,3,24,24],[5,3,21,22],[6,2,11,13],[7,4,9,9]]
Output: 50.0
First orders are 1 (customer 1, scheduled), 2 (customer 2, immediate), 5 (customer 3, scheduled) and 7 (customer 4, immediate). Two of four customers.
Input: orders = [[5,7,20,21],[2,7,20,20]]
Output: 100.0
Both rows land on day 20, so order 2 is the first by id, and it was immediate.
Input: orders = [[1,1,10,10],[2,1,5,8]]
Output: 0.0
Order 2 was placed on day 5, earlier than order 1, so it is the first order even though it appears second in the list. It was scheduled.
Fold the rows into one map keyed by customer that holds the first order seen so far, replacing it only when a row has an earlier day, or the same day and a smaller id. Then count the stored rows whose two days match and divide by the map size.
Immediate Delivery Percentage: First-Order Tally Per Customer
Click "Run" to execute your code
Test Cases
[[[1,1,1,2],[2,2,2,2],[3,1,11,12],[4,3,24,24],[5,3,21,22],[6,2,11,13],[7,4,9,9]]]Expected: 50.0[[[5,7,20,21],[2,7,20,20]]]Expected: 100.0[[[1,1,10,10],[2,1,5,8]]]Expected: 0.0[[[1,1,3,3],[2,2,4,5],[3,3,5,5]]]Expected: 66.67[[[1,1,1,1],[2,2,1,1],[3,3,1,1],[4,4,1,2],[5,5,1,2],[6,6,1,2],[7,7,1,2]]]Expected: 42.86[[[1,1,1,1],[2,1,2,2],[3,2,1,2],[4,2,3,3],[5,3,2,2],[6,3,1,1],[7,4,4,4],[8,5,1,3]]]Expected: 60.0[[[1,1,1,1],[2,2,1,2],[3,3,1,2],[4,4,1,2],[5,5,1,2],[6,6,1,2],[7,7,1,2],[8,8,1,2]]]Expected: 12.5[[[2,9,4,4],[1,9,4,6]]]Expected: 0.0store_id column onto each row and asks for the same number per store. For each store, return the immediate share among the first orders placed there as [store_id, pct] pairs sorted by store. The per-customer map is unchanged; you group its surviving rows by store before you divide. That version is worked through below.Promo Credit Pair: Complement Lookup
Problem
budget dollars and wants to spend it on exactly two catalog items whose prices add up to it. You are given prices, a list of item prices in the order they appear in the catalog.Return the indices
[i, j] with i < j of a pair whose prices sum to budget, choosing the pair whose second item appears earliest. If no such pair exists, return [-1, -1].Input encoding. Each test case is a JSON array
[prices, budget]. The function returns a two-element array of indices.Input: prices = [2,7,11,15], budget = 9
Output: [0, 1]
Item 0 at $2 and item 1 at $7 sum to $9.
Input: prices = [3,3], budget = 6
Output: [0, 1]
The two $3 items sum to $6; their earliest indices are 0 and 1.
Input: prices = [1,2,3], budget = 7
Output: [-1, -1]
No two items sum to $7.
Walk the prices once. For each item, the partner you need is budget minus its price. Ask a map whether that partner has already gone by; if so, you have the pair. Store each price against its earliest index.
Promo Credit Pair: Complement Lookup
Click "Run" to execute your code
Test Cases
[[2,7,11,15], 9]Expected: [0, 1][[3,3], 6]Expected: [0, 1][[1,2,3], 7]Expected: [-1, -1][[5,5,5], 10]Expected: [0, 1][[4,1,6,2], 8]Expected: [2, 3][[10], 10]Expected: [-1, -1]budget. This is the classic "now make it a service you extend" turn, and it maps to how Instacart likes to grow a problem across parts. You keep a set of prices seen and a latch that stays true once a pair appears. That version is worked through below.Pivot Order-Status Counts: Long-to-Wide Reshape
Problem
records, a list of rows [store_id, order_id, status] where every value is a string and each row is one order. An ops dashboard wants the same data wide, one row per store and one column per status, with a count in every cell.Return the wide table as rows of strings, the way it would be written back out as CSV. The first row is the header,
"store" followed by every distinct status in ascending string order. Then one row per store, in ascending string order of store_id, holding the store id and the count of its orders under each status column as a decimal string. A store with no orders in a status gets "0" in that cell. The column set comes from the data, so a status you have never seen still gets its column.With no records, return just the header,
[["store"]].Input encoding. Each test case is a JSON array
[records], a single list argument. The function returns the table as a list of string lists.Input: records = [["s1","o1","delivered"],["s1","o2","canceled"],["s2","o3","delivered"],["s1","o4","delivered"]]
Output: [["store","canceled","delivered"],["s1","1","2"],["s2","0","1"]]
Two statuses appear, so the header carries two count columns in sorted order. Store s2 never had a canceled order, so that cell reads "0".
Input: records = [["10","o1","picking"],["2","o2","delivered"]]
Output: [["store","delivered","picking"],["10","0","1"],["2","1","0"]]
Store ids are strings, so "10" sorts ahead of "2". Each store is missing one of the two statuses and gets a zero there.
Input: records = []
Output: [["store"]]
No rows means no statuses and no stores, so the header is the only row.
A two-level dictionary, store to status to count, filled in one pass while a set collects every status seen. Sort the statuses for the header and the stores for the rows, then walk the grid, reading 0 for any missing cell.
Pivot Order-Status Counts: Long-to-Wide Reshape
Click "Run" to execute your code
Test Cases
[[["s1","o1","delivered"],["s1","o2","canceled"],["s2","o3","delivered"],["s1","o4","delivered"]]]Expected: [["store","canceled","delivered"],["s1","1","2"],["s2","0","1"]][[["b","1","picking"],["a","2","delivered"],["b","3","delivered"],["c","4","canceled"]]]Expected: [["store","canceled","delivered","picking"],["a","0","1","0"],["b","0","1","1"],["c","1","0","0"]][[["s9","o1","delivered"]]]Expected: [["store","delivered"],["s9","1"]][[]]Expected: [["store"]][[["10","o1","picking"],["2","o2","delivered"],["10","o3","delivered"]]]Expected: [["store","delivered","picking"],["10","1","1"],["2","1","0"]][[["s1","o1","delivered"],["s1","o2","delivered"],["s1","o3","delivered"],["s1","o4","delivered"],["s1","o5","delivered"],["s1","o6","delivered"],["s1","o7","delivered"],["s1","o8","delivered"],["s1","o9","delivered"],["s1","o10","delivered"],["s1","o11","canceled"]]]Expected: [["store","canceled","delivered"],["s1","1","10"]][[["w","1","returned"],["w","2","returned"],["v","3","returned"]]]Expected: [["store","returned"],["v","1"],["w","2"]][[["s3","o1","delivered"],["s1","o2","picking"],["s2","o3","canceled"],["s1","o4","delivered"],["s3","o5","canceled"],["s2","o6","canceled"]]]Expected: [["store","canceled","delivered","picking"],["s1","0","1","1"],["s2","2","0","0"],["s3","1","1","0"]][store_id, day, order_id, status], the table pivots on the (store, day) pair and a total column lands on the right. Stage three hands you the wide table and asks for the long form back, one [store, status, count] row per cell above zero. Both are worked below. The two-level dictionary from stage one carries straight through, with the outer key growing into a tuple.Key Takeaways
- ✓Arrays, strings, and hashing are the Instacart assessment opener and the onsite warm-up. The first two CodeSignal levels are a hash aggregation and a sliding window, and the bar is code that parses the records and passes the hidden tests on the first run.
- ✓Three skeletons cover the band: a hash-map tally that folds records into a per-group summary, a sliding window over sorted events, and a complement lookup that finds a partner in one pass. Decide which the record stream needs before you type.
- ✓Instacart wraps every problem in the marketplace: fill rate at a store, the peak window a team staffs for, a promo credit spent on two items. Hear the grocery story, name the skeleton, and keep the parse step separate from the reduce step.
- ✓The staged follow-up bolts a business rule onto your own code: a minimum-orders threshold, out-of-order arrivals, a running cart service. Leave the first version with clean seams so the new rule is a filter or an extra variable, not a rewrite.
Instacart interview guideKnow what Instacart asks before you walk in.
30 coding · 5 system design · 14 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.