SevyDevy

Guide · Frontend

Frontend Machine Coding: Build a Credit Card Input Feature (Complete Interview Guide)

25 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

A complete, interview-ready guide to building a credit card input system with formatting, card detection, validation, masking, UX improvements, and real-world edge cases.

Why This Question Is Important

This is not a UI question. It is a system design question at component level. Interviewers are checking whether you can handle real-world complexity inside a single input field. A strong solution shows control over formatting, validation, UX, and edge cases — all in real-time.

What You Must Build (Interview Requirements)

  • Auto format card number (spaces, grouping)
  • Detect card type dynamically
  • Validate using Luhn algorithm
  • Support multiple card types (Visa, MasterCard, Amex, RuPay, etc.)
  • Handle expiry (MM/YY) with validation
  • Handle CVV with dynamic length (3/4 digits)
  • Mask card number (optional)
  • Handle paste, delete, cursor position
  • Provide real-time feedback

Final Expected UI

ts
Card Number:  4242 4242 4242 4242
Card Type:    VISA
Expiry:       12/28
CVV:          123
Status:       Valid ✅

Step 1: Understanding Card Types (Very Important)

Different cards have different patterns, lengths, and formatting rules. A good implementation supports these differences.

ts
const CARD_CONFIG = [
  { type: "VISA", prefix: /^4/, lengths: [13, 16, 19], cvv: 3 },
  { type: "MASTERCARD", prefix: /^5[1-5]/, lengths: [16], cvv: 3 },
  { type: "AMEX", prefix: /^3[47]/, lengths: [15], cvv: 4 },
  { type: "RUPAY", prefix: /^6/, lengths: [16], cvv: 3 },
];

Example: Amex uses 15 digits and 4-digit CVV, while Visa uses 16 digits and 3-digit CVV.

Step 2: Detect Card Type

ts
const detectCardType = (number: string) => {
  return CARD_CONFIG.find((card) =>
    card.prefix.test(number)
  )?.type || "UNKNOWN";
};

This runs on every keystroke and allows dynamic UI updates like logos or CVV length.

Step 3: Smart Formatting (Not Always 4-4-4-4)

Not all cards use same grouping. Amex uses 4-6-5 pattern.

ts
const formatCardNumber = (value: string, type: string) => {
  const clean = value.replace(/\D/g, "");

  if (type === "AMEX") {
    return clean
      .replace(/(\d{4})(\d{6})(\d{0,5})/, "$1 $2 $3")
      .trim();
  }

  return clean
    .replace(/(.{4})/g, "$1 ")
    .trim();
};

Example: AMEX → 3782 822463 10005 VISA → 4242 4242 4242 4242

Step 4: Luhn Algorithm (Validation Core)

This algorithm ensures card number is mathematically valid.

ts
const luhnCheck = (num: string) => {
  let sum = 0;
  let double = false;

  for (let i = num.length - 1; i >= 0; i--) {
    let digit = +num[i];

    if (double) {
      digit *= 2;
      if (digit > 9) digit -= 9;
    }

    sum += digit;
    double = !double;
  }

  return sum % 10 === 0;
};

Without Luhn validation, your solution is considered incomplete in interviews.

Step 5: Expiry Input (MM/YY)

ts
const formatExpiry = (value: string) => {
  const clean = value.replace(/\D/g, "");
  return clean.replace(/(\d{2})(\d{0,2})/, "$1/$2");
};

Validation logic:

ts
const validateExpiry = (value: string) => {
  const [month, year] = value.split("/");

  if (!month || !year) return false;

  const m = parseInt(month);
  const y = parseInt("20" + year);

  if (m < 1 || m > 12) return false;

  const now = new Date();
  const expiry = new Date(y, m);

  return expiry > now;
};

Step 6: CVV Handling

CVV length depends on card type.

ts
const validateCVV = (cvv: string, type: string) => {
  const config = CARD_CONFIG.find(c => c.type === type);
  return cvv.length === config?.cvv;
};

Step 7: Full React Implementation

tsx
import { useState } from "react";

export default function CreditCard() {
  const [card, setCard] = useState("");
  const [expiry, setExpiry] = useState("");
  const [cvv, setCvv] = useState("");

  const raw = card.replace(/\s/g, "");
  const type = detectCardType(raw);
  const isValid = luhnCheck(raw);

  return (
    <div>
      <input
        placeholder="Card Number"
        value={card}
        onChange={(e) => {
          const raw = e.target.value.replace(/\D/g, "");
          const type = detectCardType(raw);
          setCard(formatCardNumber(raw, type));
        }}
      />

      <p>Type: {type}</p>
      <p>Valid: {isValid ? "Yes" : "No"}</p>

      <input
        placeholder="MM/YY"
        value={expiry}
        onChange={(e) =>
          setExpiry(formatExpiry(e.target.value))
        }
      />

      <input
        placeholder="CVV"
        value={cvv}
        onChange={(e) =>
          setCvv(e.target.value.replace(/\D/g, ""))
        }
      />
    </div>
  );
}

Real User Flow (Important for Interview Explanation)

Real user flow through the card input

What the input does as the user types: detect the card brand, format the number, run Luhn, then format and validate the expiry.

Diagram connections:

  • User types: 4 to User types: 4242
  • User types: 4242 to User types full
  • User types full to User enters expiry
  • User types: 4: Detect VISA
  • User types: 4242: Format → 4242
  • User types full: 4242 4242 4242 4242 → Run Luhn → Show VALID
  • User enters expiry: 12 → 12/ → 12/28 → validated

Edge Cases You Must Handle

  • User pastes full card number
  • Deleting from middle
  • Cursor jump issue after formatting
  • Invalid characters
  • Different card lengths
  • Incomplete input states

Advanced (What Makes You Stand Out)

  • Mask card → **** **** **** 4242
  • Auto move to next field
  • Show card logo dynamically
  • Disable submit until valid
  • Add error states (red border)
  • Debounce validation

Final Takeaway

This problem tests real-world frontend engineering. A strong candidate not only formats input but builds a system that feels natural, responsive, and correct.

If you can explain card detection, Luhn validation, formatting logic, and edge cases clearly while writing clean code, you will easily clear this machine coding round.

Practise this with Lala

Practise