InterviewLegend

Arrays, Strings & Hashing

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

The Plaid angle
The interviewer is testing whether you can extend your own code, not just write it. A Plaid round is built to evolve. The strongest signal is a base solution whose grouping step you can point at and say the new rule slots in right here. Engineers who interviewed describe losing the round not by failing to find an algorithm but by hard-coding the first version so tightly that the follow-up forced a full rewrite. Structure for change from the first function.

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.

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

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

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

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

1

Recurring Charge Detection: Group and Scan

MediumSignature problem
Problem
You get a list of transactions, each a tuple (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.

How this uses the pattern

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

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

Test Cases

Input: [[["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"]
Input: [[]]Expected: []
Input: [[["A",100,1],["A",100,31]]]Expected: []
Input: [[["Gym",5000,2],["Gym",5000,33],["Gym",5000,64]]]Expected: ["Gym"]
Input: [[["X",50,1],["X",50,10],["X",50,25]]]Expected: []
Input: [[["Shop",250,1],["Shop",250,8],["Shop",250,15],["Shop",250,22]]]Expected: ["Shop"]
The Plaid angle
This is the problem most tied to Plaid, and it is a group-by-key with a subscription prompt wrapped around it. Its shape mirrors the recurring-payment detection Plaid ships in its own products, so interviewers care that you find the key fast and keep the interval logic readable. The round turns on the escalation to a 20% tolerance. Candidate reports say it arrives the moment Part 1 passes. A base solution that baked the amount into the group key has to be rebuilt, while one that grouped by merchant and compared amounts separately absorbs it in a couple of lines.
Take it further
Once fuzzy recurrence works, a natural next ask is to return not just the merchant but the detected cadence, roughly monthly or roughly weekly, by bucketing the median interval. It is a small addition on top of the same grouped scan: you already have each qualifying run's intervals, so classify their average into a cadence band and carry it out with the merchant.
2

Longest Activity Streak: Hash-Set Scan

MediumCommonly asked
Problem
An Item (one linked bank connection) posts transactions on various days. You are given a list of integer day numbers on which the account had at least one posted transaction. The list is unordered and may contain the same day more than once.

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.

How this uses the pattern

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

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

Test Cases

Input: [[4, 2, 7, 1, 3, 8, 9]]Expected: 4
Input: [[]]Expected: 0
Input: [[5, 5, 6]]Expected: 2
Input: [[10]]Expected: 1
Input: [[1, 2, 3, 4, 5, 100]]Expected: 5
The Plaid angle
Framed as account activity, this is a linear-time run scan built on a set. Plaid watches millions of Items for connection health and freshness, so "how long has this account been continuously active" is a natural product question. The interviewer is checking that you reach for a set rather than sorting, and that you know the start-of-run guard is what keeps the scan linear. A candidate who sorts first still gets the right answer but signals they missed the O(n) path this pattern is famous for.
Take it further
A heavier variant asks for the longest streak that tolerates a single missing day, since a weekend or a bank posting gap should not end a run. That turns the membership walk into a sorted sweep that may bridge one gap of size two. It's a change of approach, so flag it as the point where the set gives way to ordering.
3

Merchant Spend Rollup: Normalize, Then Group

MediumCommonly asked
Problem
Raw bank transactions carry messy descriptor strings from the payment processor, so the same merchant shows up under several spellings. You get a list of (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.

How this uses the pattern

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

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

Test Cases

Input: [[["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]]
Input: [[]]Expected: []
Input: [[["SQ *COFFEE SHOP 0123",500],["SQ *COFFEE SHOP 0123",-200],["BLUE BOTTLE",300]]]Expected: [["BLUE BOTTLE", 300], ["COFFEE SHOP", 300]]
Input: [[["PAYPAL *ETSY SELLER",1200],["PP*ETSY SELLER",800]]]Expected: [["ETSY SELLER", 2000]]
The Plaid angle
This is transaction enrichment in miniature, where the string cleanup is hard and the grouping is trivial once the key is right. Plaid normalizes raw institution descriptors into clean merchant data for its customers, so a canonicalize-then-aggregate task is squarely on-domain. The interviewer is watching whether you isolate the normalization into its own step. The refund follow-up and any future rule (a new processor prefix, a different location format) then land in that one function instead of scattering through the aggregation.
Take it further
A common extension asks for spend grouped by merchant and month rather than merchant alone, which is the same rollup with a composite (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.
Had your Plaid interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Plaid interview guide

Know what Plaid asks before you walk in.

16 coding · 5 system design · 18 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.