Arrays, Strings & Hashing
- Recognize the group-by-a-key shape that hides inside almost every Plaid transaction problem
- Reach for a hash map or set before sorting, and know when the set is enough
- Hold money as integer cents and design a base solution the interviewer can layer a fuzzy rule onto
- Work three transaction-flavored problems end to end, each with the escalation Plaid tends to add next
Most of what Plaid's coding rounds hand you is a pile of records: bank transactions, account snapshots, merchant descriptors, connection events. The first job is almost always the same. Turn that pile into something keyed, then group or scan it. That is why arrays, strings, and hashing sit under more of this loop than any other family. The problems candidates recall skew heavily toward array, string, and hash-table work. And every applied prompt, the parse-and-dedupe tasks Plaid is known for, runs on the same substrate.
The mental model to carry in is small. When records share a natural key, a merchant name, an account id, an amount, a routing number, a hash map turns "compare everything to everything" into one linear pass. When you only need to ask whether a value is present, a hash set answers in constant time and often replaces a sort entirely. Pin that key early and the rest of the problem tends to fall out. Miss it and you write nested loops the interviewer watches you regret.
How Plaid frames it
Plaid rarely states the algorithm. You get a dense, realistic prompt about financial data and you have to find the structure yourself. A prompt about "which subscriptions are recurring" is a group-by-key followed by an interval check. A prompt about "the longest stretch of days this account had activity" is a hash-set membership scan. A prompt about "roll up spend by merchant" is string normalization feeding a keyed sum. The words change, the shape does not.
The other constant is the escalation. Candidate reports describe interviewers who open with a small task and then stack a new requirement mid-call once your first version runs. The exact numbers match becomes a fuzzy match with a tolerance. The clean data grows a duplicate or a refund. So the base solution is graded twice: does it work, and is it shaped so the next requirement is a few lines rather than a rewrite. Keeping the parse, the grouping, and the decision rule in separate steps is what lets you absorb the twist without starting over.
Nearly every transaction problem here reduces to group the records by a key, then decide something per group. Get the key right and the decision, a repeated interval, a running total, a present-or-absent check, is usually a short loop over each bucket. The hard part is reading the fintech prose and naming the key, not the traversal it implies.
The patterns that carry the round
Two skeletons cover most of what this family throws at you. Learn them cold and you will spend your thinking time on the business rule instead of the mechanics.
Pattern 1: group by a composite key
When records belong together because they share one or more fields, bucket them in a single pass, then work each bucket on its own. The key can be one field or a tuple of several. A defaultdict(list) keeps the grouping to two lines and leaves the per-group logic clean.
from collections import defaultdictdef group_and_decide(records): groups = defaultdict(list) # composite key -> list of members for merchant, amount, day in records: groups[(merchant, amount)].append(day) # bucket by (merchant, amount) result = [] for key, members in groups.items(): members.sort() # order within a bucket, if the rule needs it if decide(members): # the per-group business rule lives here result.append(key) return resultThe read-through matters more than the code. The key is the modeling decision: choosing (merchant, amount) versus merchant alone is exactly the lever a follow-up pulls, because a fuzzy-amount rule wants the amount out of the key and inside the comparison. Sorting happens inside a bucket, never across all records, so it stays cheap. And the decision rule is one named function you can rewrite in place when the requirement changes.
Pattern 2: membership in a hash set
When the question is only "is this value present," drop the list into a set and ask directly. This is what lets you find a run of consecutive days in linear time without sorting, by checking for neighbors instead of ordering the whole input.
def longest_run(values): present = set(values) # O(1) membership, duplicates collapse for free best = 0 for v in present: if v - 1 in present: continue # not a run start, skip so each run counts once length = 1 while v + length in present: # walk forward only from a true start length += 1 best = max(best, length) return bestThe start-of-run guard makes the set scan linear: only begin counting at a value whose predecessor is absent. Without it you re-walk the same run from every member and the pass turns quadratic. With it, every value is visited at most twice, so the whole thing is O(n) even though it looks like a nested loop.
Asked at Plaid
Three problems in Plaid's own domain, each the base version plus the escalation an interviewer tends to add once it runs. Try Part 1 in the editor, then reveal the walkthrough and the worked follow-up. Money stays in integer cents throughout, the same discipline Plaid's data pipeline uses so a float never drifts a total off by a penny.
Recurring Charge Detection: Group and Scan
Problem
(merchant, amount_cents, day), where day is an integer day number and amount_cents is an integer. A product feature wants to surface which merchants a user has a recurring charge with.Part 1. A merchant counts as recurring if it has at least three transactions that share the same amount and are equally spaced in time, meaning three charges at a constant day-interval. Return the sorted list of distinct merchant names that have a recurring charge.
Part 2 (the escalation). Real subscriptions are not exact. Allow a 20% tolerance on both the amount and the interval, so a roughly-monthly charge that bills 30 then 31 days apart at slightly varying amounts still counts. Return the same sorted list under the fuzzy rule.
Input: txns = [ ("Netflix", 1599, 1), ("Netflix", 1599, 31), ("Netflix", 1599, 61), ("Spotify", 999, 5), ("Spotify", 999, 35), ("Spotify", 999, 66), ("Corner Store", 480, 3), ("Corner Store", 512, 25), ]
Output: ["Netflix"]
Netflix bills 1599 cents at days 1, 31, 61, gaps of exactly 30 and 30, so it is recurring. Spotify bills at days 5, 35, 66, gaps of 30 then 31, which is not exactly equal, so the strict Part 1 rule misses it. Corner Store has only two charges and they differ in amount.
Input: Part 2, same input, 20% tolerance
Output: ["Netflix", "Spotify"]
Under the fuzzy rule Spotify now qualifies: its intervals 30 and 31 differ by 1, well within 20% of 30, and its amounts are identical.
Group by a composite key, then scan each bucket. Bucket the transactions by (merchant, amount), sort each bucket by day, and look for three days in a row at a constant gap. For Part 2, move the amount out of the key and into a tolerance comparison so drifting amounts still land in the same group.
Recurring Charge Detection: Group and Scan
Click "Run" to execute your code
Test Cases
[[["Netflix",1599,1],["Netflix",1599,31],["Netflix",1599,61],["Spotify",999,5],["Spotify",999,35],["Spotify",999,66],["Corner Store",480,3],["Corner Store",512,25]]]Expected: ["Netflix"][[]]Expected: [][[["A",100,1],["A",100,31]]]Expected: [][[["Gym",5000,2],["Gym",5000,33],["Gym",5000,64]]]Expected: ["Gym"][[["X",50,1],["X",50,10],["X",50,25]]]Expected: [][[["Shop",250,1],["Shop",250,8],["Shop",250,15],["Shop",250,22]]]Expected: ["Shop"]Longest Activity Streak: Hash-Set Scan
Problem
Part 1. Return the length of the longest run of consecutive calendar days with activity.
Part 2 (the escalation). Return the actual range of that longest streak as
(start_day, end_day) rather than just its length, or None when there is no activity at all.Input: days = [4, 2, 7, 1, 3, 8, 9]
Output: 4
Days 1, 2, 3, 4 form a run of length 4. Days 7, 8, 9 form a run of length 3. The longest is 4.
Input: Part 2, same input
Output: (1, 4)
The longest run spans day 1 through day 4.
Membership in a hash set. Drop the days into a set so duplicates collapse and neighbor checks are O(1). Only start counting a run at a day whose predecessor is absent, then walk forward. For Part 2, remember where the winning run started and ended instead of only its length.
Longest Activity Streak: Hash-Set Scan
Click "Run" to execute your code
Test Cases
[[4, 2, 7, 1, 3, 8, 9]]Expected: 4[[]]Expected: 0[[5, 5, 6]]Expected: 2[[10]]Expected: 1[[1, 2, 3, 4, 5, 100]]Expected: 5Merchant Spend Rollup: Normalize, Then Group
Problem
(raw_descriptor, amount_cents) pairs. Normalize each descriptor to a canonical merchant with these rules, in order. Uppercase and trim, then strip a known processor prefix if the string starts with one ("SQ *", "TST*", "PP*", or "PAYPAL *"). Collapse the remaining whitespace, and drop a trailing token if it is all digits (a store or location number).Part 1. Group by the canonical merchant, sum the cents, and return
(merchant, total_cents) pairs sorted by merchant name.Part 2 (the escalation). Refunds arrive as negative amounts. Net them against the merchant's spend and drop any merchant whose net total is zero or below, so a fully refunded merchant disappears from the rollup.
Input: txns = [ ("SQ *COFFEE SHOP 0123", 500), ("sq *coffee shop 0456", 350), ("TST* BLUE BOTTLE 9", 700), ("BLUE BOTTLE", 300), ]
Output: [("BLUE BOTTLE", 1000), ("COFFEE SHOP", 850)]
The two Square coffee descriptors both normalize to COFFEE SHOP and sum to 850. The Toast and plain Blue Bottle descriptors normalize to BLUE BOTTLE and sum to 1000. Output is sorted by merchant.
Input: Part 2: add ("SQ *COFFEE SHOP 0123", -850)
Output: [("BLUE BOTTLE", 1000)]
The 850 of coffee spend is fully refunded to a net of zero, so COFFEE SHOP drops out.
Normalize each string to a key, then group-and-sum. Keep the normalization in its own function so the grouping stays a clean one-liner. For Part 2, the sum naturally handles negatives; you only add a filter that drops non-positive net totals at the end.
Merchant Spend Rollup: Normalize, Then Group
Click "Run" to execute your code
Test Cases
[[["SQ *COFFEE SHOP 0123",500],["sq *coffee shop 0456",350],["TST* BLUE BOTTLE 9",700],["BLUE BOTTLE",300]]]Expected: [["BLUE BOTTLE", 1000], ["COFFEE SHOP", 850]][[]]Expected: [][[["SQ *COFFEE SHOP 0123",500],["SQ *COFFEE SHOP 0123",-200],["BLUE BOTTLE",300]]]Expected: [["BLUE BOTTLE", 300], ["COFFEE SHOP", 300]][[["PAYPAL *ETSY SELLER",1200],["PP*ETSY SELLER",800]]]Expected: [["ETSY SELLER", 2000]](merchant, month) key. Because the canonical merchant already comes out of a dedicated function, you only widen the group key and the normalization stays untouched, the payoff of keeping the two steps separate.Where candidates lose the round
The failures here are rarely a missed algorithm. They are modeling and hygiene slips that a Plaid interviewer, reading your code like a pull request, spots at once and then probes.
Do
- Name the key first, then let the grouping and the per-group rule fall out of it.
- Reach for a hash set when the question is only presence, and skip the sort.
- Hold every amount as integer cents so keys and totals stay exact.
- Isolate normalization and parsing so a follow-up rule lands in one function.
- Narrate the key you chose and why, since the escalation usually targets it.
Don't
- Bake a field into the group key that a fuzzy follow-up will need to loosen.
- Compare every pair in a bucket when adjacent triples are all the rule needs.
- Sort out of habit when a set answers the neighbor question in linear time.
- Use floats for money and let a rounded value split one merchant into two keys.
- Cram parse, normalize, and decide into one function that Part 2 forces you to rewrite.
The whole family comes down to one habit: find the key, then decide per group. Choose the key so the fixed fields anchor the bucket and the drifting fields stay comparable, keep money in integer cents, and hold parse, group, and rule as separate steps. Do that and the escalation Plaid adds next is the few lines you left room for, not the rewrite that sinks the round.
Key Takeaways
- ✓Arrays, strings, and hashing sit under more of Plaid's loop than any other family, because almost every transaction prompt is a group-by-key or a set-membership scan underneath.
- ✓The base solution is graded twice: once for correctness and once for whether the fuzzy or refund escalation is a few lines rather than a rewrite. Structure for change from the first function.
- ✓Choose the key so fixed fields anchor the bucket and drifting fields stay comparable, and hold money as integer cents so keys and totals never drift.
- ✓A hash set turns a consecutive-run scan into O(n) with the start-of-run guard, and isolating normalization keeps every future rule contained to one place.
Plaid interview guideKnow what Plaid asks before you walk in.
16 coding · 5 system design · 18 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.