InterviewLegend

Arrays, Strings & Hashing

Alex Chen22 min readUpdated
In this lesson
  • 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.

Stateful API design30%
Parsing & expression eval20%
Arrays, strings & hashing18%
Graphs & relationships14%
Simulation & running stats10%
Trees & tries8%
This lesson covers the assessment and warm-up layer. Instacart does not publish a family breakdown, so these shares are ordered by how often each shape shows up in reported loops. Hashing and sliding-window sit at the start of the CodeSignal ramp and reappear as the opener on onsite rounds
The Instacart angle
The input is almost never a clean array. It is a stack of records you have to parse and reduce first. Instacart problems arrive as rows of order lines, delivery timestamps, or catalog prices, often read from a file or standard input, and the aggregation is the answer. That parse-then-reduce step is a first-class skill here, and pure algorithm drilling skips it. The code runs against predefined and hidden tests on every round, so a tally that miscounts one edge row or a window with a drifting bound compiles clean and then fails a case live. Practice in a real editor with the records in front of you, not on paper.

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.

The Instacart angle
Treat the first version as a service you are about to extend, not a puzzle you solve once. The follow-up almost always bolts a new rule onto your existing code. Candidates who do well leave clean seams. They isolate aggregation from reporting and window logic from what it measures, and keep state in a small structure you can read another value out of. When the interviewer says "now only count stores with at least twenty orders," that should be a filter you add, not a rewrite you start over. Results-over-process grading rewards the working, extensible version over a narrated brute force.

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.

python
from collections import defaultdict
def 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_group

The 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.

python
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 best

The 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.

python
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]
The Instacart angle
Deep hash-map and tree-map fluency is the load-bearing skill on this whole band, and Instacart says so in what it asks. Reported guidance singles out the hash map for its constant-time lookup and the tree map for time-ordered keys, and the assessment leans on both from the first level. The tally, the complement lookup, and the time-windowed sweep are all the same tool used three ways. Get comfortable enough that reaching for the right one is reflex, because the seventy-minute assessment does not leave room to rediscover the pattern on each task.

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.

1

Top Fill-Rate Store: Hash-Map Tally

EasyCommonly asked
Problem
You are given 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.

How this uses the pattern

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

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

Test Cases

Input: [[[1,10,1],[1,11,0],[2,20,1]]]Expected: 2
Input: [[[5,1,1],[5,2,1],[5,3,1]]]Expected: 5
Input: [[[1,10,1],[1,11,1],[2,20,1],[2,21,0]]]Expected: 1
Input: [[[3,1,0],[7,2,0]]]Expected: 3
Input: [[[9,1,1]]]Expected: 9
Input: [[[2,1,1],[2,2,0],[1,3,1],[1,4,1]]]Expected: 1
The Instacart angle
Fill rate is a real Instacart health metric, the fraction of an order a shopper can actually assemble against live shelf availability. As a coding warm-up it is a plain frequency tally, which is exactly the CodeSignal Level-1 archetype at the top of the assessment. The interviewer is watching whether you build the aggregation cleanly and handle the tie-break without a second pass, not whether you know a clever trick.
Take it further
The reported next step adds a minimum-orders threshold and asks for a ranked list instead of a single winner. Return the top k 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.
2

Busiest Delivery Window: Sliding Window

MediumCommonly asked
Problem
You are given 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.

How this uses the pattern

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

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

Test Cases

Input: [[0,10,20,30,40], 20]Expected: 3
Input: [[0,60,120], 60]Expected: 2
Input: [[5], 100]Expected: 1
Input: [[0,1,2,3,4], 1]Expected: 2
Input: [[0,100,200,300], 50]Expected: 1
Input: [[10,10,10,10], 0]Expected: 4
The Instacart angle
Staffing shoppers for the dinner rush is a live Instacart problem, and here it is the CodeSignal Level-2 archetype: a sliding window over time-ordered events. The times arrive sorted, so no binary search is needed, only two forward-moving edges. The skill it probes is keeping a valid window with a running span rather than re-counting the orders inside it on every step, which would turn a linear sweep into a quadratic one under the clock.
Take it further
A natural Instacart turn drops the "sorted" guarantee, since real order events stream in from many stores out of order. It asks for the start time of the busiest window instead of its size, so a dispatcher knows when to add shoppers. Sort first, then track the left edge of the best window as you sweep. That version is worked through below.
3

Immediate Delivery Percentage: First-Order Tally Per Customer

MediumCommonly asked
Problem
Every Instacart checkout ends with a delivery choice. You are given 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.

How this uses the pattern

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

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

Test Cases

Input: [[[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
Input: [[[5,7,20,21],[2,7,20,20]]]Expected: 100.0
Input: [[[1,1,10,10],[2,1,5,8]]]Expected: 0.0
Input: [[[1,1,3,3],[2,2,4,5],[3,3,5,5]]]Expected: 66.67
Input: [[[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
Input: [[[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
Input: [[[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
Input: [[[2,9,4,4],[1,9,4,6]]]Expected: 0.0
The Instacart angle
Instacart checkout splits into "as soon as possible" and "pick a slot", and the share wanting it now sets how many shoppers a zone needs this hour rather than tonight. Narrowing it to each customer's first order makes it a retention question, since a day-one shopper who needed groceries within the hour behaves differently from one who booked Saturday morning. On the assessment this sits at CodeSignal Level 1, with one twist on the tally. The row you keep per customer has to be the earliest, with the smaller order id settling a same-day tie, and the rows do not arrive sorted. Hidden tests lean on the rounding and on that tie, and a single-order customer still counts.
Take it further
A natural Instacart turn, in the add-a-business-rule style the panel favors, bolts a store_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.
4

Promo Credit Pair: Complement Lookup

EasySometimes asked
Problem
A customer has a promo credit worth exactly 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.

How this uses the pattern

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

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

Test Cases

Input: [[2,7,11,15], 9]Expected: [0, 1]
Input: [[3,3], 6]Expected: [0, 1]
Input: [[1,2,3], 7]Expected: [-1, -1]
Input: [[5,5,5], 10]Expected: [0, 1]
Input: [[4,1,6,2], 8]Expected: [2, 3]
Input: [[10], 10]Expected: [-1, -1]
The Instacart angle
Spending a promo credit on exactly two items is the two-sum pattern in Instacart's catalog. The point of the problem is the complement lookup: instead of checking every pair, you carry a map of prices already seen and ask whether the partner of the current item is in it. That single-pass instinct is what separates a candidate who writes the linear version from one who nests two loops and times out on the larger hidden cases.
Take it further
The reported extension turns the one-shot check into a running service: items are added to the cart one at a time, and after each addition you report whether any two items so far already sum to 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.
5

Pivot Order-Status Counts: Long-to-Wide Reshape

MediumFrequently asked
Problem
Order events leave the order service one row at a time, in long form. You are given 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.

How this uses the pattern

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

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

Test Cases

Input: [[["s1","o1","delivered"],["s1","o2","canceled"],["s2","o3","delivered"],["s1","o4","delivered"]]]Expected: [["store","canceled","delivered"],["s1","1","2"],["s2","0","1"]]
Input: [[["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"]]
Input: [[["s9","o1","delivered"]]]Expected: [["store","delivered"],["s9","1"]]
Input: [[]]Expected: [["store"]]
Input: [[["10","o1","picking"],["2","o2","delivered"],["10","o3","delivered"]]]Expected: [["store","delivered","picking"],["10","1","1"],["2","1","0"]]
Input: [[["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"]]
Input: [[["w","1","returned"],["w","2","returned"],["v","3","returned"]]]Expected: [["store","returned"],["v","1"],["w","2"]]
Input: [[["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"]]
The Instacart angle
Some Instacart loops, for data-adjacent and full-stack roles, open with a reshape round built on nested dictionaries. The order pipeline writes long rows because events arrive one order at a time, while every retailer-facing dashboard and ops report reads wide, one line per store with a cell per status. Counting is the easy half. The column set has to come out of the rows rather than a hard-coded list, or the hidden test carrying a status the examples never showed fails on its header alone. Zero-fill matters as much, since a store with no canceled orders still owes that cell a "0". Sorting both axes is what makes two runs over the same input emit the same bytes.
Take it further
Stage two adds a day, so the rows become [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.
Had your Instacart interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Instacart interview guide

Know what Instacart asks before you walk in.

30 coding · 5 system design · 14 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.