Implement Luhn-based card validation and inference
Company: Stripe
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Take-home Project
Quick Answer: A Stripe software-engineer take-home that builds up a credit-card validation suite using the Luhn checksum and VISA/MASTERCARD/AMEX brand patterns. It progresses from exact validation and brand identification to an UNKNOWN fallback, analytic wildcard counting per brand, and single-error correction that enumerates all valid original numbers.
Luhn Card Validation + Brand Identification
Constraints
- Input contains only ASCII digit characters 0-9.
- Length is not guaranteed to be 15 or 16; non-matching lengths simply fail the brand check.
- Empty string is Luhn-valid (sum 0) but matches no brand -> INVALID_CHECKSUM.
Examples
Input: ('4242424242424242',)
Expected Output: 'VISA'
Explanation: 16 digits, starts with 4, Luhn-valid -> VISA (a real Visa test card).
Input: ('5555555555554444',)
Expected Output: 'MASTERCARD'
Explanation: 16 digits, starts with 55, Luhn-valid -> MASTERCARD.
Input: ('371449635398431',)
Expected Output: 'AMEX'
Explanation: 15 digits, starts with 37, Luhn-valid -> AMEX.
Input: ('4242424242424241',)
Expected Output: 'INVALID_CHECKSUM'
Explanation: Last digit changed; the Luhn checksum no longer holds.
Input: ('1234567812345670',)
Expected Output: 'INVALID_CHECKSUM'
Explanation: Luhn-valid but starts with 1 and matches no brand pattern -> INVALID_CHECKSUM in task 1.
Input: ('378282246310005',)
Expected Output: 'AMEX'
Explanation: 15 digits, starts with 37, Luhn-valid -> AMEX.
Hints
- Write a reusable luhn_ok(t) helper first; tasks 2-4 all reuse it.
- Process digits right-to-left and double every second one, subtracting 9 when the doubled digit exceeds 9.
- Brand identification only happens AFTER Luhn passes. A Luhn-valid number with an unknown prefix/length is still INVALID_CHECKSUM in this first task.
Card Validation with UNKNOWN Fallback
Constraints
- Input contains only ASCII digit characters 0-9.
- The only behavioral change from task 1 is the final fallthrough: UNKNOWN instead of INVALID_CHECKSUM when Luhn passes but no brand matches.
Examples
Input: ('4242424242424242',)
Expected Output: 'VISA'
Explanation: Brand match takes precedence over the UNKNOWN fallback.
Input: ('5555555555554444',)
Expected Output: 'MASTERCARD'
Explanation: Starts with 55, 16 digits, Luhn-valid -> MASTERCARD.
Input: ('378282246310005',)
Expected Output: 'AMEX'
Explanation: 15-digit AMEX test card.
Input: ('1234567812345670',)
Expected Output: 'UNKNOWN'
Explanation: Luhn-valid but no brand pattern matches -> UNKNOWN (was INVALID_CHECKSUM in task 1).
Input: ('4242424242424241',)
Expected Output: 'INVALID_CHECKSUM'
Explanation: Fails Luhn, so INVALID_CHECKSUM regardless of prefix.
Input: ('6011111111111117',)
Expected Output: 'UNKNOWN'
Explanation: 16-digit Discover test card, Luhn-valid, starts with 60 -> matches no known brand -> UNKNOWN.
Hints
- This is task 1 with a single line changed: the final return becomes 'UNKNOWN' instead of 'INVALID_CHECKSUM'.
- INVALID_CHECKSUM is reserved exclusively for numbers that FAIL the Luhn check.
- A 16-digit Luhn-valid number starting with 6 (e.g. a Discover card) is UNKNOWN here, since only VISA/MASTERCARD/AMEX patterns are recognized.
Wildcard Card Counting (Analytic, Not Brute Force)
Constraints
- Input contains digits 0-9 and '*' wildcards only.
- Number of wildcards can be large; 10^(free-1) must be computed analytically, never enumerated.
- Counts can exceed 32-bit range; Python ints are unbounded, but Java/C++ solutions would need BigInteger for very large patterns.
- Brands are emitted in fixed order VISA, MASTERCARD, AMEX; zero-count brands are omitted.
Examples
Input: ('****424242424242',)
Expected Output: ['VISA,100', 'MASTERCARD,50']
Explanation: 16-long, 4 leading stars. VISA: pos0 star -> 1 choice (4), 3 free -> 1*10^2=100. MASTERCARD: pos0 -> 1 (5), pos1 -> 5 (1-5), 2 free -> 5*10^1=50. AMEX needs length 15, excluded.
Input: ('4*4242424242424*',)
Expected Output: ['VISA,10']
Explanation: Fixed first digit 4 -> only VISA. 2 stars, pos0 fixed; one star is a free non-prefix position -> 10^(2-1)=10. Only the last star is free (the second char is a non-prefix position too) so f=2 -> 10.
Input: ('5*5*555555554444',)
Expected Output: ['MASTERCARD,5']
Explanation: Starts with 5 -> only MasterCard possible. pos1 star -> 5 prefix choices, 1 free star -> 5*10^0=5.
Input: ('3*1449635398431',)
Expected Output: ['AMEX,1']
Explanation: 15-long, starts with 3 -> only AMEX. pos1 star limited to {4,7} (2 choices) but it is the LAST free degree of freedom; here pos1 is a prefix position so choices=2 and free=0 -> enumerate {4,7}: only one fill is Luhn-valid -> 1.
Input: ('4242424242424242',)
Expected Output: ['VISA,1']
Explanation: No wildcards; the single fully-specified number is Luhn-valid VISA -> count 1.
Input: ('****************',)
Expected Output: ['VISA,100000000000000', 'MASTERCARD,50000000000000']
Explanation: 16 free-ish stars. VISA: 1*10^14. MASTERCARD: 1*5*10^13. Computed analytically, never enumerated.
Input: ('1234567812345670',)
Expected Output: []
Explanation: 16 digits, no wildcards, starts with 1 -> matches no brand prefix -> empty list.
Hints
- Treat each brand independently: first check the length matches, then check every fixed prefix digit is compatible.
- A wildcard inside the prefix region is NOT free: it is limited to the brand's allowed prefix values (VISA leading '*' -> 1 choice; MasterCard second '*' -> 5 choices).
- Luhn is a single linear (mod-10) constraint, so it removes exactly one degree of freedom: with f free wildcards the valid completions number 10^(f-1). With zero free wildcards, just enumerate the (tiny) prefix-wildcard fills and Luhn-check each.
Single-Error Card Reconstruction
Constraints
- The observed string ends with a single trailing '?'.
- Exactly one error occurred; the candidate generator inverts all four edit types and deduplicates (different edits can yield the same original).
- Only Luhn-valid candidates that map to a real brand (VISA/MASTERCARD/AMEX) survive; UNKNOWN-shaped numbers are discarded.
- Results are sorted ascending by the numeric string and formatted as "NUMBER,BRAND".
Examples
Input: ('4242424242424252?',)
Expected Output: ['4242424242424242,VISA', '4242424242424259,VISA', '4242424242424952,VISA', '4242424242427252,VISA', '4242424242494252,VISA', '4242424242724252,VISA', '4242424249424252,VISA', '4242424272424252,VISA', '4242424942424252,VISA', '4242427242424252,VISA', '4242494242424252,VISA', '4242724242424252,VISA', '4249424242424252,VISA', '4272424242424252,VISA', '4942424242424252,VISA']
Explanation: A near-valid 16-digit observed string; the true original 4242424242424242 is recovered by a single changed-digit inversion, alongside other VISA originals one edit away.
Input: ('371449635398431?',)
Expected Output: ['371449635398431,AMEX']
Explanation: The observed (minus '?') is already a valid 15-digit AMEX; the only branded Luhn-valid reconstruction within one edit is itself.
Input: ('424242424242424?',)
Expected Output: ['4242342424242424,VISA', '4242422424242424,VISA', '4242424024242424,VISA', '4242424214242424,VISA', '4242424242042424,VISA', '4242424242424234,VISA', '4242424242424242,VISA', '4242424242424424,VISA', '4242424242462424,VISA', '4242424248242424,VISA', '4244242424242424,VISA', '4624242424242424,VISA']
Explanation: Observed is 15 digits; inserting a digit (undoing a removed digit) reconstructs 16-digit VISA originals including 4242424242424242.
Input: ('00?',)
Expected Output: []
Explanation: Too short to ever become a valid 15/16-digit branded card via a single edit -> no reconstructions.
Hints
- Strip the trailing '?' first, then generate candidates for all four inverse operations into a set so duplicates collapse.
- Insertion (to undo a removed digit) and deletion (to undo an added digit) change the length by one - that is how a 15-digit AMEX or 16-digit VISA original can emerge from an off-by-one observed string.
- Re-validate each candidate with the SAME Luhn + brand rules from tasks 1-2; keep only the ones that are both Luhn-valid and brand-matched.