Virtual Card Case Study: Trade-offs, Card Number Rule Checks, and Debugging a Filter

Read the full interview experience this question came from →

Quick Overview

A three-part virtual credit card case study: weigh the benefits and costs of giving merchants virtual card numbers, check five sample numbers against length, Luhn checksum and prefix rules, and debug a Python filter whose conditionals accept invalid numbers. It tests product reasoning, careful manual analysis and evidence-driven debugging.

Virtual Card Case Study: Trade-offs, Card Number Rule Checks, and Debugging a Filter

Company: Capital One

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Onsite

You are working through a technical case study about virtual credit card numbers. A virtual card number is a card number that the issuer generates on request and links to a customer's existing account, so the customer can give a merchant the virtual number instead of the number printed on the physical card. The case has three connected parts: weigh the trade-offs of virtual cards, check a set of virtual card numbers against validation conditions, and debug the code that filters numbers on those conditions. The case's original rules, numbers and code were not recorded. So that the exercise is self-contained, this practice version supplies the representative rules, numbers and code below. A number is an acceptable virtual card number only if it meets all three rules: - **R1 (length):** after removing spaces, it consists of exactly 16 digits and nothing else. - **R2 (checksum):** it passes the Luhn check. Starting from the rightmost digit (the check digit) and moving left, double every second digit, and subtract 9 from any doubled value above 9. The number passes when the sum of all the resulting digits is a multiple of 10. - **R3 (prefix):** its first digit is `4` or `5`. ### Clarifying Questions - Are the rules applied to the number as typed or to a normalized string, and is any separator other than a space allowed? - Should the filter return the original strings or the normalized numbers, and must it keep the input order? - Should a malformed entry, such as an empty string or one containing letters, be skipped silently or reported? - In Part 1, whose point of view matters most: the customer's, the merchant's or the issuer's? ### Part 1 — Trade-offs of virtual cards Before any data work, discuss whether customers should give merchants virtual card numbers instead of their physical card number. Lay out the benefits and the costs for the customer, the merchant and the issuer, and say when you would not recommend a virtual number. ```hint Follow the card after checkout Think past the purchase itself: refunds, recurring charges, a merchant that is breached a year later, and purchases where the physical card must be shown. ``` #### What This Part Should Cover - Benefits, tied to how a separate number limits what a leak or a misbehaving merchant can do - Costs and failure scenarios for each party, including what happens after the purchase - A recommendation that names when virtual numbers fit and when they do not ### Part 2 — Which conditions does each number meet? | ID | Number | |---|---| | A | `4111 1111 1111 1111` | | B | `4111 1111 1111 1112` | | C | `5500 0000 0000 0004` | | D | `6011 0000 0000 0004` | | E | `4111 1111 1111 116` | For each number, state which of R1, R2 and R3 it meets. Show the checksum arithmetic for at least two numbers, then list the numbers that are acceptable. ```hint Reuse your work A and B differ only in their last digit. Work out one checksum carefully, then ask how much that single digit can move the sum. ``` #### What This Part Should Cover - Each rule evaluated on its own for every number, without stopping at the first failure - Checksum arithmetic with the doubled positions counted from the right - The final list of acceptable numbers, naming the failing rule for each rejected one ### Part 3 — Debug the filter `filter_cards` should return, in input order, the input strings that meet all three rules. The test below fails. ```python def luhn_ok(number: str) -> bool: total = 0 for i, ch in enumerate(number): d = int(ch) if i % 2 == 0: d *= 2 if d > 9: d -= 9 total += d return total % 10 == 0 def filter_cards(numbers: list[str]) -> list[str]: result = [] for raw in numbers: number = raw.replace(" ", "") if len(number) == 16 or luhn_ok(number) and number[0] in "45": result.append(raw) return result ``` ```python def test_filter_cards(): cards = [ "4111 1111 1111 1111", "4111 1111 1111 1112", "5500 0000 0000 0004", "6011 0000 0000 0004", "4111 1111 1111 116", ] assert filter_cards(cards) == ["4111 1111 1111 1111", "5500 0000 0000 0004"] ``` Explain what `filter_cards` actually returns for this test, and why. Then find every defect in the conditionals, including any that this test does not reach, give a corrected version, and add tests that would have caught each defect. ```hint Parenthesize what Python sees Evaluate the `if` condition by hand for number B, writing in the parentheses that Python actually applies. ``` ```hint Test the helper on its own Treat `luhn_ok` as a reusable helper and call it directly with inputs the filter happens not to give it today: other lengths and unexpected characters. ``` #### What This Part Should Cover - The failing test traced to its root cause through the actual evaluation - Latent defects the given test does not reach, each with a test that exposes it - A corrected filter that keeps input order and never raises on malformed input ### What a Strong Answer Covers - A link between the business trade-offs and the reason numbers are validated before they are accepted - Careful hand analysis before touching code, reused as the expected output of the tests - Evidence-driven debugging: reproduce, trace, fix, then add regression tests - Readable conditionals, one rule per clause or helper, ordered so cheap guards protect later checks ### Follow-up Questions - Which typing errors does the Luhn check always catch, and which common ones can slip through? - How would you change the code if the program started issuing 15-digit numbers or added a prefix, without rewriting the conditional each time? - Beyond hand-picked examples, how would you test the filter, for example with generated numbers? - Where should these checks run: in the client, at checkout, in the authorization path, or in several places?

Overview: A three-part virtual credit card case study: weigh the benefits and costs of giving merchants virtual card numbers, check five sample numbers against length, Luhn checksum and prefix rules, and debug a Python filter whose conditionals accept invalid numbers. It tests product reasoning, careful manual analysis and evidence-driven debugging.

Read the full Capital One Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Capital One
Capital One logo
Capital One
Sep 18, 2026
mediumSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

You are working through a technical case study about virtual credit card numbers. A virtual card number is a card number that the issuer generates on request and links to a customer's existing account, so the customer can give a merchant the virtual number instead of the number printed on the physical card. The case has three connected parts: weigh the trade-offs of virtual cards, check a set of virtual card numbers against validation conditions, and debug the code that filters numbers on those conditions.

The case's original rules, numbers and code were not recorded. So that the exercise is self-contained, this practice version supplies the representative rules, numbers and code below. A number is an acceptable virtual card number only if it meets all three rules:

  • R1 (length): after removing spaces, it consists of exactly 16 digits and nothing else.
  • R2 (checksum): it passes the Luhn check. Starting from the rightmost digit (the check digit) and moving left, double every second digit, and subtract 9 from any doubled value above 9. The number passes when the sum of all the resulting digits is a multiple of 10.
  • R3 (prefix): its first digit is 4 or 5 .

Clarifying Questions Guidance

  • Are the rules applied to the number as typed or to a normalized string, and is any separator other than a space allowed?
  • Should the filter return the original strings or the normalized numbers, and must it keep the input order?
  • Should a malformed entry, such as an empty string or one containing letters, be skipped silently or reported?
  • In Part 1, whose point of view matters most: the customer's, the merchant's or the issuer's?

Part 1 — Trade-offs of virtual cards

Before any data work, discuss whether customers should give merchants virtual card numbers instead of their physical card number. Lay out the benefits and the costs for the customer, the merchant and the issuer, and say when you would not recommend a virtual number.

What This Part Should Cover Guidance

  • Benefits, tied to how a separate number limits what a leak or a misbehaving merchant can do
  • Costs and failure scenarios for each party, including what happens after the purchase
  • A recommendation that names when virtual numbers fit and when they do not

Part 2 — Which conditions does each number meet?

IDNumber
A4111 1111 1111 1111
B4111 1111 1111 1112
C5500 0000 0000 0004
D6011 0000 0000 0004
E4111 1111 1111 116

For each number, state which of R1, R2 and R3 it meets. Show the checksum arithmetic for at least two numbers, then list the numbers that are acceptable.

What This Part Should Cover Guidance

  • Each rule evaluated on its own for every number, without stopping at the first failure
  • Checksum arithmetic with the doubled positions counted from the right
  • The final list of acceptable numbers, naming the failing rule for each rejected one

Part 3 — Debug the filter

filter_cards should return, in input order, the input strings that meet all three rules. The test below fails.

def luhn_ok(number: str) -> bool:
    total = 0
    for i, ch in enumerate(number):
        d = int(ch)
        if i % 2 == 0:
            d *= 2
            if d > 9:
                d -= 9
        total += d
    return total % 10 == 0


def filter_cards(numbers: list[str]) -> list[str]:
    result = []
    for raw in numbers:
        number = raw.replace(" ", "")
        if len(number) == 16 or luhn_ok(number) and number[0] in "45":
            result.append(raw)
    return result
def test_filter_cards():
    cards = [
        "4111 1111 1111 1111",
        "4111 1111 1111 1112",
        "5500 0000 0000 0004",
        "6011 0000 0000 0004",
        "4111 1111 1111 116",
    ]
    assert filter_cards(cards) == ["4111 1111 1111 1111", "5500 0000 0000 0004"]

Explain what filter_cards actually returns for this test, and why. Then find every defect in the conditionals, including any that this test does not reach, give a corrected version, and add tests that would have caught each defect.

What This Part Should Cover Guidance

  • The failing test traced to its root cause through the actual evaluation
  • Latent defects the given test does not reach, each with a test that exposes it
  • A corrected filter that keeps input order and never raises on malformed input

What a Strong Answer Covers Guidance

  • A link between the business trade-offs and the reason numbers are validated before they are accepted
  • Careful hand analysis before touching code, reused as the expected output of the tests
  • Evidence-driven debugging: reproduce, trace, fix, then add regression tests
  • Readable conditionals, one rule per clause or helper, ordered so cheap guards protect later checks

Follow-up Questions Guidance

  • Which typing errors does the Luhn check always catch, and which common ones can slip through?
  • How would you change the code if the program started issuing 15-digit numbers or added a prefix, without rewriting the conditional each time?
  • Beyond hand-picked examples, how would you test the filter, for example with generated numbers?
  • Where should these checks run: in the client, at checkout, in the authorization path, or in several places?
Loading comments...