- Interview Questions
- /
- Problems
- /
- Invalid Transactions
Invalid Transactions
Flag card transactions that are too large or that happen in two cities within an hour for the same person. A test of careful parsing and grouping before comparing. Solve it below in Python or JavaScript, then read the walkthrough.
Problem
You're given a list of card transactions. Each one is a string "name,time,amount,city": the person's name, the time in minutes, the amount, and the city where it happened.
A transaction is suspicious if either of these is true:
- its amount is greater than
1000, or - the same person made another transaction in a different city at most
60minutes before or after it (the gap|t1 - t2| ≤ 60).
Both transactions in a too-close pair are suspicious. Return every suspicious transaction string, in any order. If the same string appears twice in the input and is suspicious, include it twice.
Example 1
- Input
- transactions = ["mia,10,300,paris", "mia,55,80,berlin"]
- Output
- ["mia,10,300,paris", "mia,55,80,berlin"]
Mia paid in Paris and in Berlin 45 minutes apart, so both are suspicious.
Example 2
- Input
- transactions = ["tom,100,1500,cairo", "tom,400,20,cairo"]
- Output
- ["tom,100,1500,cairo"]
The first is over 1000. The second is in the same city, so it is fine.
Example 3
- Input
- transactions = ["kai,0,50,quito", "lee,30,50,tokyo", "kai,61,50,tokyo"]
- Output
- []
Kai's two payments are 61 minutes apart, one minute too far. Lee is a different person.
Constraints
- 1 ≤ transactions.length ≤ 1000
- Names and cities are 1 to 10 lowercase English letters.
- 0 ≤ time ≤ 1000
- 0 ≤ amount ≤ 2000
Practice
Runs 12 tests in your browser. Python loads on the first run (about 6 MB, then cached). Press ⌘/Ctrl + Enter to run.
Get asked Invalid Transactions in a real interview?
Interview Solver gives you a working solution live, during your actual coding interview. Invisible to screensharing.
How to solve Invalid Transactions
Pattern: Parse, group by key, compare within groups
The amount rule is a check on one record at a time. The city rule is the interesting one: it only ever compares transactions that share a name. So parse every string once into (name, time, amount, city, index), group by name, and only compare inside each group.
Track suspicion by index, not by string, so a transaction is reported once even if it fails both rules, and duplicate strings are each reported. With n at most 1000, comparing every pair inside a group is fast enough. If you want better, sort each group by time and only look at neighbours within the 60-minute window.
Approach 1: Every pair across the whole list
For each transaction, re-parse and compare it against every other one, checking name, city and time gap. Works, but re-parses strings on every comparison and wastes time on pairs with different names.
Time O(n²) · Space O(n)
Approach 2: Group by name, sort by time, scan the window
Group parsed records by name and sort each group by time. For each record, walk forward while the time gap is at most 60 and mark both when the cities differ. Fast when groups are large and spread out, but a window full of same-city records can still be quadratic.
Time O(n log n + pairs in window) · Space O(n)
Approach 3: Group by name, compare inside each group
- Parse each string once and keep its index.
- Mark index
iif its amount is over 1000. - Group indices by name. Inside each group, for every pair with different cities and a time gap of at most 60, mark both.
- Return the original strings for every marked index.
Simple, correct, and fast for the given limits.
Time O(n + Σ g²) for group sizes g, O(n²) worst case · Space O(n)
| Approach | Time | Space |
|---|---|---|
| Every pair across the whole list | O(n²) | O(n) |
| Group by name, sort by time, scan the window | O(n log n + pairs in window) | O(n) |
| Group by name, compare inside each group | O(n + Σ g²) for group sizes g, O(n²) worst case | O(n) |
Solution code
Try the practice editor first. Both solutions pass every test above.
Invalid Transactions solution in Python
from collections import defaultdict
def invalid_transactions(transactions: list[str]) -> list[str]:
parsed = []
for t in transactions:
name, time, amount, city = t.split(",")
parsed.append((name, int(time), int(amount), city))
bad = [amount > 1000 for _, _, amount, _ in parsed]
by_name = defaultdict(list)
for i, (name, _, _, _) in enumerate(parsed):
by_name[name].append(i)
for group in by_name.values():
for a in range(len(group)):
i = group[a]
for b in range(a + 1, len(group)):
j = group[b]
if parsed[i][3] != parsed[j][3] and abs(parsed[i][1] - parsed[j][1]) <= 60:
bad[i] = bad[j] = True
return [t for t, flag in zip(transactions, bad) if flag]Invalid Transactions solution in JavaScript
function invalidTransactions(transactions) {
const parsed = transactions.map((t) => {
const [name, time, amount, city] = t.split(',');
return { name, time: Number(time), amount: Number(amount), city };
});
const bad = parsed.map((p) => p.amount > 1000);
const byName = new Map();
parsed.forEach((p, i) => {
if (!byName.has(p.name)) byName.set(p.name, []);
byName.get(p.name).push(i);
});
for (const group of byName.values()) {
for (let a = 0; a < group.length; a++) {
const i = group[a];
for (let b = a + 1; b < group.length; b++) {
const j = group[b];
if (parsed[i].city !== parsed[j].city && Math.abs(parsed[i].time - parsed[j].time) <= 60) {
bad[i] = bad[j] = true;
}
}
}
}
return transactions.filter((_, i) => bad[i]);
}Common mistakes
- Using `< 60` instead of `≤ 60`. A gap of exactly 60 minutes counts.
- Collecting results in a set of strings, which drops a duplicate transaction that should appear twice.
- Adding a transaction twice when it fails both rules, or when it pairs with two different partners.
- Comparing times as strings ("100" < "60" alphabetically). Convert to integers when parsing.
- Only marking the later transaction of a too-close pair. Both are suspicious.
What interviewers look for
Clarify the rules before coding: is the window inclusive, are both members of a pair invalid, and should duplicates be reported twice. Interviewers use this problem to see whether you read requirements carefully, so asking is a plus.
Then structure the code: one parse step, one boolean array of "invalid" flags indexed by position, and a final pass that maps flags back to strings. Saying "I mark by index so each transaction is reported exactly once" shows you have thought about the double-counting trap.
Follow-up questions
How would you make the city check faster when one person has thousands of transactions?
Sort that person's records by time and use a sliding window of the last 60 minutes, keeping a count of records per city in the window. A record is invalid if the window holds any record from another city, which you can tell from the number of distinct cities in the window. Doing it in both directions (or marking partners as you go) gives O(g log g) per group.
What if transactions arrive as a live stream?
Keep, per person, a queue of their transactions from the last 60 minutes. When a new one arrives, drop expired entries, check the queue for other cities, and flag both sides. Memory stays proportional to recent activity, not history.
What if the input strings could contain malformed records?
Validate during parsing: four fields, numeric time and amount, non-empty name and city. Decide with the interviewer whether bad records are skipped, reported as invalid, or raise an error, and keep that logic in the parse step so the rule checks stay clean.
Companies that ask Invalid Transactions
Invalid Transactions is tagged by 75 companies in our interview dataset. The score shows how often it comes up at each company.
- Sap Concur100%
- Stripe89.7%
- Wixcom87.3%
- D2l84%
- Airtasker84%
- Alcon82%
- Canva81%
- ADP81%
- GitLab79%
- Bloomberg78.7%
- Quantiphi77%
- Dialpad76%
- Iqvia75%
- Wex75%
- Clever73%
- Fivetran73%
- Ticketmaster73%
- Motorola Solutions72%
- Appier71%
- Redfin70%
- Aptiv69%
- Springworks Therapeutics67%
- Onit67%
- Pinwheel66%
and 51 more.
Practice next
Get live help in the real interview
Working solutions in real time during your actual interview. Invisible to screensharing.
Start Free TrialNo credit card required