Burner Card Numbers: Trade-offs, Hand Checks and a Buggy Validator
Capital OneSoftware Engineer · OnsiteLast updated
A three-part fintech case: weigh the trade-offs of disposable card numbers, check sample numbers by hand against format, Luhn and issuer-range rules, then find three bugs in a filter that rejects valid numbers and accepts invalid ones.
Your team at a consumer bank is adding burner card numbers to the mobile app. A customer can generate a fresh 16-digit number tied to their real account, use it with a single merchant or subscription, and freeze or delete it at any time without touching the physical card. This case study has three connected parts.
Validation rules used in this exercise
An input string is an acceptable burner number only if it satisfies all three rules:
- R1 (format): it contains only digits, spaces and hyphens, and after removing spaces and hyphens there are exactly 16 digits.
- R2 (checksum): the digits pass the Luhn check. Moving left from the rightmost digit, double every second digit (the rightmost digit itself is not doubled), subtract 9 from any doubled value above 9, and add everything up. The total must be a multiple of 10.
- R3 (issuer range): the first two digits form a number from 51 to 55 inclusive.
Part 1: Product trade-offs
In 5 to 7 minutes of discussion, lay out the benefits and costs of burner numbers from the customer's, the bank's and the merchant's points of view. Cover fraud exposure, recurring billing and refunds, operational and support cost, and at least one way the feature could hurt the customer experience. End with a recommendation and the metrics you would watch after launch.
Part 2: Check the samples by hand
Without running code, decide for each sample which rules it passes and whether it is acceptable. Show your Luhn arithmetic for at least two of them.
A 5342 1987 0012 3456
B 5248-4850-6950-6990
C 4991 3944 1177 1512
D 5518 2000 3344 5560 1
E 5351 0273 4646 8695
F 5266 1111 2222 333O
(The last character of F is the letter O.)
Part 3: Debug the filter
A teammate wrote this filter. It is supposed to return the acceptable inputs in their original order.
def filter_burner_numbers(raw_numbers):
accepted = []
for raw in raw_numbers:
digits = raw.replace(" ", "").replace("-", "")
if not digits.isdigit() or len(digits) < 16:
continue
prefix = int(digits[:2])
if prefix < 51 and prefix > 55:
continue
total = 0
for i, ch in enumerate(digits):
d = int(ch)
if i % 2 == 1:
d *= 2
if d > 9:
d -= 9
total += d
if total % 10 == 0:
accepted.append(raw)
return accepted
On the six samples it returns B, C, D, E. The correct answer is A, B. Find every bug, explain how each one produces the observed output, fix the function, and say which tests you would add so that each bug would have been caught.
Hints
Hint 1
For Part 1, organize by stakeholder (customer, bank, merchant), then by benefit and cost. Do not forget refunds and recurring charges.
Hint 2
In Luhn, the rightmost digit is never doubled. Write the digits right to left before doubling.
Hint 3
Read each condition in the filter aloud with a concrete value. Can 'prefix < 51 and prefix > 55' ever be true?
Hint 4
Compare the filter's output with your Part 2 table. Each wrongly accepted sample points to a different check.
Follow-up questions
- How would you generate burner numbers so that they are unique, pass Luhn, and cannot be guessed?
- Where should validation live (client, API gateway, issuing service), and what should each layer check?
- A merchant retries a recurring charge on a frozen burner number every hour. How should the system respond?
- How would you design the burner-number-to-account mapping so that authorization stays under 20 ms at peak?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.