Katkat

Prepaid Travel Card Case Study: Checks, Trade-offs, Buggy Filter

Capital OneSoftware Engineer · Onsite

Last updated

A three-part case study on prepaid travel cards: weigh the trade-offs of issuing them, manually check sample numbers against length, Luhn, and prefix rules, then debug Python filter code whose conditionals let invalid numbers through. Tests product reasoning, careful manual rule-checking, and evidence-driven debugging.

You're working through a case study about prepaid travel cards: single-purpose card numbers a bank issues for a traveler to load with a fixed amount and spend abroad, separate from the traveler's main account or physical card. The case has three connected parts: weigh the trade-offs, manually check sample numbers against validation rules, then debug the code meant to enforce those rules.

For this practice version, a number is an acceptable prepaid travel card number only if it meets all three rules:

  • R1 (length): after removing spaces, it is exactly 16 digits and nothing else.
  • R2 (checksum): it passes the Luhn check — starting from the rightmost digit and moving left, double every second digit and subtract 9 from any doubled value over 9; the number passes when the sum of every resulting digit is a multiple of 10.
  • R3 (prefix): its first digit is 6 or 7 (this exercise's placeholder range for prepaid travel products).

Clarifying Questions

  • Are the rules applied to the string as typed, or to a normalized version with spaces stripped — and is any other separator allowed?
  • Should a malformed entry (empty string, letters mixed in) be skipped silently or reported?
  • In Part 1, whose perspective matters most: the traveler's, the merchant's abroad, or the issuing bank's?

Part 1 — Trade-offs of a prepaid travel card

Before touching any numbers, discuss whether a bank should offer travelers a prepaid card loaded with a fixed amount, instead of letting them use their regular card abroad. Cover benefits and costs for the traveler, for a merchant who accepts it, and for the issuing bank, and say when you would not recommend one.

Think past the purchase itself: what happens on a refund, a currency-conversion dispute, a card that's lost mid-trip, or a travel partner (hotel, airline) that holds a deposit against the card.

What This Part Should Cover

  • Traveler benefits (capped loss exposure, budgeting, no foreign-transaction surprises on the main account) weighed against costs (reload friction, a refund outliving the trip, declined holds if the balance is too tight)
  • Merchant and issuer considerations: fraud exposure, dispute handling, and the cost of supporting a separate product line
  • A clear answer to "when would you not recommend this"

Part 2 — Check five numbers by hand

Evaluate each number below against R1, R2, and R3, and state the final verdict:

1. 6011 0009 9013 9424
2. 7992 7398 7130 1111
3. 6011000990139425
4. 4111 1111 1111 1111
5. 601100099013942

Part 3 — Debug the filter

Here is the filter meant to keep only valid numbers, but it accepts numbers that should fail:

def buggy_filter(raws):
    out = []
    for raw in raws:
        num = raw.replace(" ", "")
        if len(num) <= 16 and num.isdigit():
            if passes_luhn(num) or passes_prefix(num):
                out.append(raw)
    return out

Find every bug, explain why each one lets an invalid number through, and fix the function so it matches R1-R3 exactly.

Hints

Hint 1

For Part 1, separate 'benefit to the traveler' from 'benefit to the bank' explicitly — a capped-loss card is good for the traveler but means the bank forgoes some interchange on larger purchases, which is a real cost to weigh.

Hint 2

For Part 2, work the Luhn check digit by digit from the rightmost position, and double-check which position (even or odd from the right) actually gets doubled.

Hint 3

For Part 3, test each condition in buggy_filter against a 15-digit number and separately against a correctly-16-digit number that fails only the prefix rule — each test isolates one bug.

Hint 4

Ask what the correct combinator should be when three independent rules must *all* hold, versus when any one of them is enough.

Follow-up questions

  • How would you extend the prefix rule to support multiple valid ranges (e.g. several BIN prefixes) without rewriting passes_prefix for each new range?
  • How would you unit-test is_valid so each of R1, R2, and R3 has an isolated failing case, independent of the other two rules?
  • If a malformed entry like an empty string or one with letters should be reported rather than silently skipped, how would you change the filter's return type?
  • How would this validation logic change if the card number length could vary by issuer?