PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates algorithmic problem-solving and implementation skills in string processing, checksum validation (Luhn), pattern matching, combinatorics for counting masked completions, and single-error recovery for payment-card numbers.

  • Medium
  • Stripe
  • Coding & Algorithms
  • Software Engineer

Implement Card Validation and Recovery System

Company: Stripe

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Take-home Project

##### Question Design and implement a payment-card validation system supporting: Basic VISA validation (16-digit numbers starting with 4) using the Luhn checksum. Multi-network validation for VISA, MASTERCARD (16 digits, prefixes 51- 55) and AMEX (15 digits, prefixes 34 or 37), returning UNKNOWN_NETWORK or INVALID_CHECKSUM when appropriate. Redacted cards containing 1-5 ‘*’ wildcards: count all valid completions per network and output counts sorted alphabetically by network. Corrupted cards ending in ‘?’ with exactly one error (one digit changed or two adjacent digits swapped): enumerate all possible original valid card numbers with their network names, sorted numerically. Follow all formatting and performance constraints described in the prompt.

Quick Answer: This question evaluates algorithmic problem-solving and implementation skills in string processing, checksum validation (Luhn), pattern matching, combinatorics for counting masked completions, and single-error recovery for payment-card numbers.

Part 1: Basic VISA Validation Using the Luhn Checksum

Implement a validator for basic VISA card numbers. A card is valid if it contains exactly 16 digits, starts with digit 4, and passes the Luhn checksum algorithm. Return True for a valid VISA number and False otherwise.

Constraints

  • 0 <= len(card_number) <= 32
  • card_number may contain digits or other characters
  • A valid VISA number must be exactly 16 characters, all digits, and start with 4

Examples

Input: ('4111111111111111',)

Expected Output: True

Explanation: This is a 16-digit number starting with 4 and it passes Luhn.

Input: ('4012888888881881',)

Expected Output: True

Explanation: Another valid VISA test number.

Input: ('4111111111111112',)

Expected Output: False

Explanation: The format is VISA-like, but the Luhn checksum fails.

Input: ('5100000000000008',)

Expected Output: False

Explanation: The number passes neither the VISA prefix rule nor the VISA network rule because it does not start with 4.

Input: ('',)

Expected Output: False

Explanation: Edge case: an empty string is not a valid card number.

Solution

def solution(card_number):
    def luhn_valid(s):
        total = 0
        should_double = False
        for ch in reversed(s):
            digit = ord(ch) - ord('0')
            if should_double:
                digit *= 2
                if digit > 9:
                    digit -= 9
            total += digit
            should_double = not should_double
        return total % 10 == 0

    if len(card_number) != 16:
        return False
    if not card_number.isdigit():
        return False
    if not card_number.startswith('4'):
        return False
    return luhn_valid(card_number)

Time complexity: O(n), where n is the length of card_number. Space complexity: O(1).

Hints

  1. For Luhn, process digits from right to left and double every second digit.
  2. If a doubled digit becomes greater than 9, subtract 9 from it before adding to the checksum.

Part 2: Multi-Network Card Validation

Implement validation for three payment-card networks: VISA, MASTERCARD, and AMEX. VISA cards have 16 digits and start with 4. MASTERCARD cards have 16 digits and prefixes 51 through 55. AMEX cards have 15 digits and prefixes 34 or 37. If the number does not match any supported network pattern, return UNKNOWN_NETWORK. If it matches a network pattern but fails Luhn, return INVALID_CHECKSUM. Otherwise return the network name.

Constraints

  • 0 <= len(card_number) <= 32
  • card_number may contain digits or other characters
  • Network matching is based on exact length and prefix
  • Checksum is evaluated only after a supported network pattern is matched

Examples

Input: ('4111111111111111',)

Expected Output: 'VISA'

Explanation: The number matches VISA rules and passes Luhn.

Input: ('5555555555554444',)

Expected Output: 'MASTERCARD'

Explanation: The prefix 55 and length 16 identify MASTERCARD, and the checksum is valid.

Input: ('378282246310005',)

Expected Output: 'AMEX'

Explanation: The prefix 37 and length 15 identify AMEX, and the checksum is valid.

Input: ('4111111111111112',)

Expected Output: 'INVALID_CHECKSUM'

Explanation: The number matches VISA format but fails the Luhn checksum.

Input: ('',)

Expected Output: 'UNKNOWN_NETWORK'

Explanation: Edge case: an empty input cannot match any supported card network.

Solution

def solution(card_number):
    def luhn_valid(s):
        total = 0
        should_double = False
        for ch in reversed(s):
            digit = ord(ch) - ord('0')
            if should_double:
                digit *= 2
                if digit > 9:
                    digit -= 9
            total += digit
            should_double = not should_double
        return total % 10 == 0

    def detect_network(s):
        if not s.isdigit():
            return None
        if len(s) == 16:
            if s[0] == '4':
                return 'VISA'
            if s[:2] in {'51', '52', '53', '54', '55'}:
                return 'MASTERCARD'
        if len(s) == 15 and s[:2] in {'34', '37'}:
            return 'AMEX'
        return None

    network = detect_network(card_number)
    if network is None:
        return 'UNKNOWN_NETWORK'
    if not luhn_valid(card_number):
        return 'INVALID_CHECKSUM'
    return network

Time complexity: O(n), where n is the length of card_number. Space complexity: O(1).

Hints

  1. Separate the network-detection logic from the Luhn checksum logic.
  2. If a number has an unsupported length or prefix, do not run checksum-based classification; return UNKNOWN_NETWORK.

Part 3: Redacted Card Wildcard Completion Counts

A redacted card number contains digits and between 1 and 5 wildcard characters, where each wildcard is written as *. Each * can be replaced by any digit from 0 to 9. Count how many completions produce a valid card for each supported network: AMEX, MASTERCARD, and VISA. Return counts for all three networks sorted alphabetically by network name.

Constraints

  • 1 <= len(redacted) <= 16
  • redacted contains only digits and * characters
  • 1 <= number of * characters <= 5
  • Supported networks use the same length, prefix, and Luhn rules as Part 2

Examples

Input: ('411111111111111*',)

Expected Output: [['AMEX', 0], ['MASTERCARD', 0], ['VISA', 1]]

Explanation: Only replacing * with 1 produces the valid VISA number 4111111111111111.

Input: ('41111111111111**',)

Expected Output: [['AMEX', 0], ['MASTERCARD', 0], ['VISA', 10]]

Explanation: The prefix and length force VISA; among the 100 endings, exactly 10 pass Luhn.

Input: ('5*55555555554444',)

Expected Output: [['AMEX', 0], ['MASTERCARD', 1], ['VISA', 0]]

Explanation: Only the completion with prefix 55 and a valid checksum is accepted.

Input: ('37828224631000*',)

Expected Output: [['AMEX', 1], ['MASTERCARD', 0], ['VISA', 0]]

Explanation: Only replacing * with 5 produces the valid AMEX number 378282246310005.

Input: ('****',)

Expected Output: [['AMEX', 0], ['MASTERCARD', 0], ['VISA', 0]]

Explanation: Edge case: no 4-digit completion can match any supported network length.

Solution

def solution(redacted):
    from itertools import product

    network_names = ['AMEX', 'MASTERCARD', 'VISA']
    counts = {name: 0 for name in network_names}

    def luhn_valid(s):
        total = 0
        should_double = False
        for ch in reversed(s):
            digit = ord(ch) - ord('0')
            if should_double:
                digit *= 2
                if digit > 9:
                    digit -= 9
            total += digit
            should_double = not should_double
        return total % 10 == 0

    def detect_network(s):
        if not s.isdigit():
            return None
        if len(s) == 16:
            if s[0] == '4':
                return 'VISA'
            if s[:2] in {'51', '52', '53', '54', '55'}:
                return 'MASTERCARD'
        if len(s) == 15 and s[:2] in {'34', '37'}:
            return 'AMEX'
        return None

    wildcard_positions = []
    chars = list(redacted)
    for i, ch in enumerate(chars):
        if ch == '*':
            wildcard_positions.append(i)

    for replacement in product('0123456789', repeat=len(wildcard_positions)):
        for pos, digit in zip(wildcard_positions, replacement):
            chars[pos] = digit
        candidate = ''.join(chars)
        network = detect_network(candidate)
        if network is not None and luhn_valid(candidate):
            counts[network] += 1

    return [[name, counts[name]] for name in network_names]

Time complexity: O(10^w * n), where w is the number of wildcards and n is the string length. Space complexity: O(n), excluding the returned output.

Hints

  1. With at most 5 wildcards, there are at most 100000 completions, which is small enough to enumerate directly.
  2. After filling the wildcards, reuse the same network-detection and Luhn validation steps.

Part 4: Corrupted Card Recovery

A corrupted card record is a string of digits followed by a trailing ? marker. The marker is not part of the card number. The digits before ? may differ from an original valid card in exactly one way: either one digit was changed to another digit, or two adjacent digits were swapped. Enumerate every possible original valid card number and its network name. Supported networks are AMEX, MASTERCARD, and VISA using the same rules as earlier parts.

Constraints

  • 1 <= len(corrupted) <= 17
  • corrupted ends with ?
  • All characters before ? are digits
  • The observed number length is expected to be 15 or 16 for recoverable cards
  • For a digit-change error, the original digit must be different from the observed digit
  • For an adjacent-swap error, the swap must change the observed string, so equal adjacent digits do not generate a candidate

Examples

Input: ('6111111111111111?',)

Expected Output: [['4111111111111111', 'VISA']]

Explanation: Changing the first digit from 6 back to 4 recovers a valid VISA number.

Input: ('4909000000000004?',)

Expected Output: [['4099000000000004', 'VISA'], ['4900900000000004', 'VISA'], ['4990000000000004', 'VISA']]

Explanation: Several adjacent swaps involving 0 and 9 produce valid VISA originals; results are sorted numerically.

Input: ('840000000000009?',)

Expected Output: [['340000000000009', 'AMEX']]

Explanation: Changing the first digit from 8 back to 3 recovers a valid AMEX number.

Input: ('4111111111111111?',)

Expected Output: []

Explanation: Edge case: the observed number is valid, but no different valid original can be obtained by one digit change or one meaningful adjacent swap.

Solution

def solution(corrupted):
    if not corrupted.endswith('?'):
        return []

    observed = corrupted[:-1]
    if not observed.isdigit():
        return []

    def luhn_valid(s):
        total = 0
        should_double = False
        for ch in reversed(s):
            digit = ord(ch) - ord('0')
            if should_double:
                digit *= 2
                if digit > 9:
                    digit -= 9
            total += digit
            should_double = not should_double
        return total % 10 == 0

    def detect_network(s):
        if not s.isdigit():
            return None
        if len(s) == 16:
            if s[0] == '4':
                return 'VISA'
            if s[:2] in {'51', '52', '53', '54', '55'}:
                return 'MASTERCARD'
        if len(s) == 15 and s[:2] in {'34', '37'}:
            return 'AMEX'
        return None

    found = {}

    def add_if_valid(candidate):
        network = detect_network(candidate)
        if network is not None and luhn_valid(candidate):
            found[candidate] = network

    n = len(observed)

    for i in range(n):
        original_digit = observed[i]
        for digit in '0123456789':
            if digit == original_digit:
                continue
            candidate = observed[:i] + digit + observed[i + 1:]
            add_if_valid(candidate)

    for i in range(n - 1):
        if observed[i] == observed[i + 1]:
            continue
        chars = list(observed)
        chars[i], chars[i + 1] = chars[i + 1], chars[i]
        candidate = ''.join(chars)
        add_if_valid(candidate)

    return [[number, found[number]] for number in sorted(found, key=lambda x: int(x))]

Time complexity: O(n^2), because O(n) candidates are generated and each validation/candidate construction costs O(n). Space complexity: O(n), excluding the returned output.

Hints

  1. Generate candidates by trying every single-position digit replacement and every adjacent swap, then validate each candidate.
  2. Use a set or dictionary to avoid returning the same valid original more than once.
Last updated: Jun 22, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Assign Reviewers from Changed Files - Stripe (medium)
  • Generate Account Email Notifications - Stripe (medium)
  • Calculate Transaction Fees - Stripe (medium)
  • Build an Account Transfer Ledger - Stripe (medium)
  • Implement Validation and String Compression - Stripe (hard)