Visa Interview Questions · 2026

Visa Interview Questions (2026): What They Actually Ask

Visa runs a process that surprises a lot of candidates who come in expecting a standard Big Tech loop. It is more breadth-focused than you might anticipate: coding, yes, but also object-oriented design fundamentals, OS concepts, REST API principles, and, at senior levels, a full system design round built around payment-scale traffic. The payments context is not decorative. Interviewers will probe whether you understand why idempotency matters in a network where a transaction request can arrive twice, why eventual consistency is not always acceptable when money is moving, and what "millions of transactions per second" actually demands from an architecture.

This page covers what the 2026 Visa software engineering loop actually looks like, drawn from candidate reports on Glassdoor, GeeksforGeeks, TechPrep, and LeetCode discuss threads from candidates who completed it between 2024 and early 2026. The questions below are verified from those sources. I have noted where something is role-level-specific or varies by team.

3-8 weeksProcess
4-5Rounds
LC MediumCoding
Virtual onsiteFormat

Easy questions

19

Track a running sum and a global maximum. At each index, decide whether to extend the current subarray or start fresh. If the running sum plus the current element is less than the element alone, reset. Update the global max after each decision.

Reported from multiple 2024 and 2025 Visa onsite rounds in both Java and Python. Common follow-up: "What if all elements are negative?" The answer: the maximum subarray is the single largest negative element, so you need to handle the initialization carefully rather than starting at zero.

java
public int maxSubArray(int[] nums) {
    int maxSum = nums[0];
    int currentSum = nums[0];

    for (int i = 1; i < nums.length; i++) {
        currentSum = Math.max(nums[i], currentSum + nums[i]);
        maxSum = Math.max(maxSum, currentSum);
    }
    return maxSum;
}

Min-heap of size N. Iterate through the array; push each element onto the heap. Once the heap exceeds size N, pop the minimum. At the end, the heap's root is the Nth largest. This runs in O(n log N) time and O(N) space, which is strictly better than full sort for large arrays and small N.

Reported in a Visa 2024 technical round in Bangalore. Interviewers asked for time complexity analysis after the solution, and then asked whether the approach changes if the array is too large to fit in memory (it does: external merge sort or a distributed approach).

Classic Two Sum: HashMap storing value-to-index mappings. O(n) time, O(n) space. Sorted array variant: two pointer from both ends, move left pointer up if sum is too small, right pointer down if too large. The sorted variant is O(n) time and O(1) space, which interviewers sometimes ask you to derive after you give the HashMap answer.

Reported in the OA and in live rounds across multiple 2024 and 2025 candidate reports. Not a trick question at Visa, it is used as a warm-up to see if you know both approaches and can choose appropriately based on constraints.

Floyd's tortoise and hare. Two pointers start at the head, one moves one node at a time, the other moves two. If the fast pointer reaches null, there is no cycle. If the two pointers ever land on the same node, a cycle exists. No hashmap of visited nodes needed, so the space cost stays O(1) against the hashmap approach's O(n).

Reported as a warm-up question in several 2025 and 2026 Visa new-grad rounds, usually before the harder DSA problem in the same session. The real follow-up is finding the cycle's starting node: reset one pointer to the head, then move both pointers one step at a time. Wherever they meet next is the start of the loop. Candidates who only memorize the detection half get stuck here.

java
public boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }
    return false;
}

Encapsulation: bundling data and methods that operate on it within a class, and restricting direct access through access modifiers (private fields with public getters). Inheritance: a subclass acquiring properties and behavior from a parent class, useful for sharing code without duplication. Polymorphism: the same method name behaving differently based on the object type, achieved through method overriding (runtime) or overloading (compile time). Abstraction: hiding implementation details and exposing only what a caller needs, done in Java with abstract classes and interfaces.

Reported across multiple Visa 2024 and 2025 technical rounds. Interviewers do not want dictionary definitions. They want a real example, preferably one from your resume. "Encapsulation in my Spring Boot service: the OrderService exposes a placeOrder() method; callers do not need to know that it validates, persists, and publishes an event inside."

Multiprocessing: multiple CPUs or CPU cores executing processes simultaneously, true parallelism. Multitasking: a single CPU switching between multiple processes rapidly, giving the illusion of simultaneity through context switching (the OS scheduler manages who runs when). Multithreading: multiple threads within a single process sharing the same memory space, lighter than process switching because threads share heap and code segments, only stack and registers are per-thread.

Reported specifically in multiple Visa new-grad rounds in 2024 and 2025 in Bangalore. Interviewers follow up on deadlock conditions (mutual exclusion, hold-and-wait, no preemption, circular wait) and how to prevent them (lock ordering, timeouts, lock-free data structures).

Abstract classes can hold state (instance variables), have constructors, and provide partial implementations. Use them when you want to share code between closely related classes or need a common base with some default behavior. Interfaces define a contract. In Java 8+, interfaces can have default and static methods, but they still cannot hold instance state. Use interfaces when you want to define a capability that unrelated classes can implement, or when you need multiple inheritance (a class can implement many interfaces but extend only one class).

Reported in multiple Visa rounds. The real follow-up question at Visa is: "In your Spring Boot project, where did you choose an abstract class and why?" Have an answer tied to something on your resume.

Tokenization replaces a sensitive card number (the PAN, or Primary Account Number) with a non-sensitive surrogate value (the token) for use in transactions and storage. The real PAN is stored only in the token vault, a PCI-DSS-compliant secure system. Merchants store and transmit tokens, so even if their system is breached, the attacker gets tokens that cannot be used without the vault's mapping. Visa's network-level tokens (device tokens, EMV tokens for contactless and chip) are unique per device and per merchant, so a token leaked from one merchant cannot be replayed at another.

At a senior level, interviewers probe the token lifecycle: provisioning, cryptographic binding to a device, and how token revocation works without disrupting the cardholder. This is genuine Visa domain knowledge that filters senior candidates.

The question that trips up the most candidates who did not research before their loop. Interviewers are listening for whether you know what Visa actually does versus a generic "I'm excited about fintech" answer. Strong answers name a specific product, technology, or initiative: VisaNet's authorization speed, Visa's developer platform and open API program, the fraud detection infrastructure, the move to real-time payment rails, or the role of tokenization in making contactless payments secure at scale.

The "Obsess About Customers" principle is relevant here. Interviewers want you to connect your interest in the technology to the end impact on the cardholder or merchant, not just to the engineering challenge.

Be specific about what you cut and what you did not. "We skipped unit tests for the config layer to hit the demo deadline, then added them in the next sprint" is a real answer. Vague "I balanced both" answers tell interviewers nothing. The follow-up is almost always: "Was that the right call?" Have an honest answer, even if you paid for the shortcut later. Owning a misjudgment with a clear lesson is more credible than a framing where every trade-off worked out perfectly.

Maps to the "Execute with Excellence" principle. Visa interviewers care about delivery, but they also care whether you have thought carefully about what you deferred and why.

Reported in multiple Visa lead rounds from 2024 and 2025. The specific version from the 2024 Bangalore new-grad round: "Tell me about a time you had to work with an unfamiliar technology. How did you approach it?" Interviewers want to hear your actual ramp method: what you read first, who you asked, what you built to verify your understanding, and how long it took. "I read the docs" is the floor. "I built a small throwaway to verify my understanding of the API before touching production code" is the ceiling.

Maps to the "Collaborate as One Visa" principle. Interviewers want to see that your first instinct is to understand the root cause, not to escalate or cover for them silently. Was the scope unclear? Did they hit a technical blocker they did not ask for help on? Are there outside pressures? The strongest answers describe a specific conversation you had, what you learned from it, and how you adjusted either the work or the support to get the delivery back on track.

One version of this from a 2025 Visa lead round: "Your team member falls behind and the release is in three days. Walk me through what you do." The framing makes it a judgment question, not a values question. Have a concrete sequence of actions, not just intentions.

Candidates report this almost word for word in Visa hiring-manager rounds. Name the actual setback, a dependency broke, a teammate left mid-sprint, a spec changed two weeks before ship, not a vague "things got hard." Then explain what you personally changed: scope, sequencing, who you looped in, what got cut. Maps to "Execute with Excellence."

The follow-up interviewers use to separate strong answers from rehearsed ones: "was the setback really unexpected, or was there a signal earlier that got missed?" A candidate who admits they saw a warning sign two days before and didn't escalate it comes across better than one who insists nothing could have been foreseen.

Most experienced-hire candidates report 3 to 8 weeks from application to verbal offer. New grad on-campus tracks at universities in India have run as fast as one week when Visa is running a placement drive. The longest gap is usually between the technical onsite and the offer call, which can take 1 to 2 weeks after your final round.

Both platforms appear in candidate reports from 2024 and 2025, with CodeSignal more common in recent accounts. The format is consistent regardless of platform: four questions, roughly 70 to 90 minutes, ranging from easy to medium-hard. Candidates report that CodeSignal's scoring weights partial completion, so finishing three questions cleanly is better than attempting four and leaving two half-done.

Java is the dominant language at Visa's engineering teams and appears most often in candidate reports. Python is accepted for coding rounds and the OA. C++ is rarely mentioned. If your strongest language is Python, that is fine for the OA and live coding, but be aware that interviewers may still ask Java-specific questions about memory management, generics, or Spring Boot if Java appears anywhere on your resume.

Yes, though the depth varies by round. Most candidates encounter at least one domain-grounding question in the hiring manager or lead round, typically "what do you know about how Visa's authorization works?" or "what motivates you to work in payments?" Senior candidates face deeper questions about idempotency, tokenization, and PCI compliance in the system design round. Spending 30 minutes reading Visa's developer documentation on the authorization flow before your loop is worth it.

Usually not as a standalone round. New grad technical rounds include design elements, typically an LRU cache with pseudocode and a basic schema design for an order delivery system, but not a full 45-minute system design interview. Full system design rounds appear at mid-level (L4) and above. If you are a new grad and system design appears in your loop, it is likely a lighter version embedded in the second technical round.

Visa publishes four leadership principles: Lead Courageously, Obsess About Customers, Collaborate as One Visa, and Execute with Excellence. The behavioral round maps questions to these, though interviewers do not always say which principle they are evaluating. "Lead Courageously" drives questions about technical disagreements and taking initiative without being asked. "Obsess About Customers" drives questions about your motivation for working in payments and your understanding of cardholder or merchant impact. Have at least one STAR story per principle ready.

Medium questions

16

DFS or BFS approach. Iterate through every cell. When you hit a '1', increment the island count and then DFS to mark all connected land cells as visited (flip them to '0' or use a visited set). The next unvisited '1' starts a new island.

Reported in the CodeSignal OA from a 2024 new-grad candidate, and again in a 2025 Bangalore onsite live round. The 2D-grid water flow variant (finding cells where water can flow to both oceans) also appeared in a 2024 OA, same DFS structure applied twice from different borders.

java
public int numIslands(char[][] grid) {
    int count = 0;
    for (int i = 0; i < grid.length; i++) {
        for (int j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == '1') {
                count++;
                dfs(grid, i, j);
            }
        }
    }
    return count;
}

private void dfs(char[][] grid, int r, int c) {
    if (r < 0 || c < 0 || r >= grid.length || c >= grid[0].length
            || grid[r][c] != '1') return;
    grid[r][c] = '0'; // mark visited
    dfs(grid, r + 1, c);
    dfs(grid, r - 1, c);
    dfs(grid, r, c + 1);
    dfs(grid, r, c - 1);
}

Use an integer counter instead of an explicit stack. Increment on open bracket, decrement on close bracket. If the counter ever goes negative, a closing bracket appeared before a matching opener, return false. If it ends at zero, valid. This handles single bracket types only; for mixed bracket types you still need a stack or a character counter per type.

Specifically reported as a follow-up coding question in a Visa 2024 on-campus technical round, where the interviewer first asked the stack-based version and then said "now do it without a stack." The no-stack constraint is the actual question; candidates who do not know this variant often stall here.

BFS with a deque. Track which level you are on. On even-numbered levels, add nodes to the result list left-to-right. On odd-numbered levels, add right-to-left (or use a flag to reverse the level's list before appending). Process all nodes in the current level before moving to the next.

Reported from a Visa 2024 new-grad technical round. The alternating direction variant (also called zigzag traversal) catches candidates who only know the standard BFS template. The interviewer will ask for the approach before you code it.

python
from collections import deque

def zigzagLevelOrder(root):
    if not root:
        return []
    result, queue, left_to_right = [], deque([root]), True
    while queue:
        level_size = len(queue)
        level = deque()
        for _ in range(level_size):
            node = queue.popleft()
            if left_to_right:
                level.append(node.val)
            else:
                level.appendleft(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(list(level))
        left_to_right = not left_to_right
    return result

Build a 2D DP table where dp[i][w] is the max value achievable using the first i items with weight capacity w. For each item, you either skip it (dp[i-1][w]) or include it if its weight fits (dp[i-1][w - weight[i]] + value[i]). Take the max of both choices.

Reported from a Visa onsite coding round. The follow-up is almost always: "Can you reduce the space complexity?" Yes, with a 1D rolling array, since each row only depends on the previous row. The space drops from O(n*W) to O(W).

java
public int knapsack(int[] weights, int[] values, int capacity) {
    int n = weights.length;
    int[] dp = new int[capacity + 1];

    for (int i = 0; i < n; i++) {
        // iterate backwards to avoid using item i twice
        for (int w = capacity; w >= weights[i]; w--) {
            dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }
    return dp[capacity];
}

Two approaches. First: treat the matrix as a flattened sorted array and binary search by converting a 1D index to row/column with division and modulo. Second (for matrices sorted such that each row starts where the last ended): start at the top-right corner. If the current value is too large, move left. If too small, move down. O(m+n) time.

Reported in a Visa coding round from 2025. The interviewer accepted both solutions but asked the candidate to analyze which approach works for which matrix structure. The top-right walk-down only works for specific matrix orderings; candidates who apply it to the wrong variant get incorrect results.

Recurse from the root. If the current node is null or equal to either target node, return it up the call stack. Otherwise recurse into the left and right subtrees. If both sides come back non-null, the current node sits between the two targets and is the answer. If only one side is non-null, pass that result further up.

Reported in Visa technical rounds right after the zigzag level-order question, most likely because both test whether a candidate can reason about a tree recursively rather than pattern-matching a memorized template. Some interviewers narrow it to a binary search tree instead, where you can skip the recursion and just compare values against the root to decide which side to descend into.

java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q) return root;
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    if (left != null && right != null) return root;
    return left != null ? left : right;
}

A plain binary search breaks because the array is not fully sorted end to end, but one of the two halves around any midpoint always is. Check whether the left half (from low to mid) is sorted by comparing nums[low] to nums[mid]. If it is, decide whether the target falls inside that sorted range; if not, the target must be in the right half, and vice versa. Narrow the search space by one half each iteration, same O(log n) bound as standard binary search.

Binary search on arrays is one of the categories candidates report most often across Visa's 2025 and 2026 online assessments, alongside tree and stack problems. The rotated variant specifically catches people who reach for a linear scan the moment the array stops looking cleanly sorted.

java
public int search(int[] nums, int target) {
    int lo = 0, hi = nums.length - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (nums[mid] == target) return mid;
        if (nums[lo] <= nums[mid]) {          // left half sorted
            if (nums[lo] <= target && target < nums[mid]) hi = mid - 1;
            else lo = mid + 1;
        } else {                               // right half sorted
            if (nums[mid] < target && target <= nums[hi]) lo = mid + 1;
            else hi = mid - 1;
        }
    }
    return -1;
}

Virtual memory requires translating virtual addresses to physical addresses via a page table, but page tables sit in RAM. Without caching, every memory access would require at least one additional RAM lookup just to find the physical address. The TLB is a small, fast hardware cache of recent virtual-to-physical address translations in the CPU. A TLB hit means the translation is served in a cycle or two. A TLB miss falls back to walking the page table in RAM, which is expensive, then loads the result into the TLB for future accesses.

Reported in a Visa 2024 new-grad technical round alongside questions about the OS kernel types (monolithic vs. microkernel vs. hybrid). At Visa, these questions arise because the payments processing infrastructure runs on Linux at high transaction throughput, and understanding OS fundamentals is considered baseline.

Atomicity: the entire transaction either commits or rolls back, a payment cannot debit one account without crediting the other. Consistency: the database moves from one valid state to another, foreign keys and balance constraints hold after every transaction. Isolation: concurrent transactions do not interfere with each other, the right isolation level (read committed, repeatable read, serializable) depends on the use case and tolerated anomalies. Durability: once a transaction commits, it survives crashes, usually guaranteed by write-ahead logging.

At Visa specifically, isolation level choice matters at scale. Serializable isolation prevents all anomalies but blocks concurrent transactions on the same rows. Repeatable Read prevents phantom reads but allows them in some edge cases. For payment authorization, most production systems choose Read Committed with explicit locking on critical rows rather than full serializable isolation.

Monolithic: all components in one deployable unit. Simpler to develop and test initially, no network overhead between components, easier to maintain local transactions. Gets hard to scale when different parts of the app have different load profiles, and a bug in any module can bring down the whole service. Microservices: components deployed independently, communicate over APIs. Lets teams scale, deploy, and update services independently. The cost: distributed systems complexity, network latency between services, harder to maintain data consistency across service boundaries.

Reported in Visa 2024 lead rounds. Visa's own infrastructure is microservices-based, so interviewers genuinely want to know if you understand the trade-offs at payment scale, not just the textbook pitch for microservices.

The card reader at the merchant captures the card data and sends it to the acquiring bank (the bank that processes payments for the merchant). The acquiring bank forwards the authorization request through VisaNet to the issuing bank (the bank that issued the card to the cardholder). The issuing bank checks: sufficient balance or credit, no fraud flags, the card is not expired or blocked. It sends an approve or decline response back through the same chain. This entire round-trip must complete in under about 2 seconds, with most of the budget spent at the issuing bank. VisaNet itself targets sub-100ms for its portion of the routing.

You do not need to know this to write a merge interval algorithm. But Visa interviewers in the hiring manager round have asked "what do you know about how our core authorization network works?" Multiple candidates who researched this before their loop reported it as a differentiator.

An operation is idempotent if executing it multiple times produces the same result as executing it once. In payments, network failures cause clients to retry requests without knowing if the original succeeded. A non-idempotent payment API that processes each request as a new transaction will double-charge the cardholder on retry. The standard fix: require clients to include a client-generated idempotency key in the request headers. The server stores completed results against the key. On retry, it returns the stored result instead of processing again. The key must have a TTL long enough to cover retry windows (typically 24 hours) but short enough to manage storage costs.

This question appeared in Visa system design rounds in 2025. The follow-up: "How do you handle an idempotency key collision where two different clients happen to generate the same key?" In practice, keys are scoped per API key or merchant ID, so collision probability is negligibly small. But the correct architectural answer is to scope keys per tenant.

The "Lead Courageously" principle in practice. Interviewers want to see that you can hold a position with evidence and update with evidence. Pure deference to seniority is a red flag. So is the opposite: "I was right and they were wrong and I refused to move." Strong answers name the actual technical trade-off at stake, what evidence you brought, what the outcome was, and what you think in hindsight. If the group's decision turned out to be the right call, say so. That is actually a better answer than one where you were right and everyone eventually agreed.

This question runs long at Visa. Interviewers typically spend 15 to 20 minutes on one project from your resume, probing the architecture decisions, the trade-offs you made, and whether you know why they were the right calls. Come prepared to go deep on one project rather than skimming three. The "impact" they want to hear about is in concrete terms: reduced latency by X%, handled Y more requests per second, reduced fraud false-positive rate by Z basis points. Round numbers with no explanation of how you measured get follow-ups.

Interviewers are listening for defensiveness more than the content of the feedback itself. A weak answer spends most of its time explaining why the feedback was wrong or unfair. A stronger one names the specific feedback, what you felt when you first heard it, and what you actually changed afterward, even if the change was small. If part of the feedback still doesn't sit right with you, say that too, and explain how you separated the part you accepted from the part you pushed back on. Pretending every piece of feedback you've ever gotten was fully correct reads as rehearsed.

Different from the teammate-falling-behind question. The friction here isn't a shared team member missing a deliverable, it's two teams with genuinely different priorities, product wants a ship date, security wants a review window, a partner team owns a dependency on their own timeline. Maps to "Collaborate as One Visa."

Interviewers want the specific mechanism you used, not just that you "communicated a lot." Did you find a shared incentive, escalate to a shared manager, or restructure the ask so the other side's cost dropped? "I sent more emails and eventually they came around" isn't a mechanism, and Visa interviewers will ask what you actually did differently.

Hard questions

7

Two-phase commit puts a coordinator in charge: every participant locks its resources and votes, and only if everyone votes yes does the coordinator tell them to commit. It gives strong consistency, but the coordinator is a single point of failure and every participant holds its locks for the full round trip. That does not hold up once transaction volume gets into the millions per second.

The Saga pattern breaks the transaction into a chain of local transactions, each with a compensating action if a later step fails. Debit the source account, then credit the destination; if the credit fails, run a compensating reverse-debit instead of rolling back a distributed lock. The trade-off is that there is no isolation between steps, so something could briefly observe a debit with no matching credit yet, meaning every downstream reader has to tolerate that window. Interviewers then push on orchestration versus choreography: a central saga coordinator sequencing each step is easier to reason about and debug, while services reacting to each other's events scales further but gets harder to trace when something goes wrong. I'd lean orchestration for anything touching money, mostly because an auditable, replayable sequence matters more here than raw throughput.

Partition tolerance is not optional once a system spans more than one data center, so the real choice under CAP is consistency or availability. Not every layer of a payment platform should make the same choice. For the authorization decision itself, most candidates argue for availability: approving or declining against the last known good balance and fraud signal beats holding a merchant's checkout open for seconds while nodes reconcile who has the correct count.

The ledger and settlement layer is the opposite case. Two nodes independently confirming the same debit against two different balances is not a degraded experience, it is a correctness bug with a dollar amount attached. The answer interviewers want is a split system: keep authorization on an available, eventually-consistent path with a reconciliation step downstream, and keep the ledger writes on a consistent path, a single writer per account shard or a consensus protocol, even when that costs latency. Reported in 2025 and 2026 Visa senior design rounds as a direct follow-up once a candidate has already designed the happy-path gateway.

First thing I'd check is whether idempotency keys are actually being generated once per user action or regenerated on every client retry. That's a surprisingly common bug: the client times out, retries, but generates a fresh key each time because the key was created inside the retry loop instead of before it. If that's the case, the backend is doing its job correctly and the fix is entirely client-side.

If the key is stable across retries, then the bug is almost always a check-then-act race in the idempotency store itself. A service reads "have I seen this key," sees nothing because two requests arrived within milliseconds of each other, and both proceed to process the payment before either has written the key back. The fix is to make the claim atomic: an INSERT with a unique constraint on the idempotency key column, or a Redis SETNX with a TTL, where the second request's write fails and it just returns the first request's cached result instead of reprocessing.

Where this gets genuinely tricky is when the lock has a TTL and processing takes longer than that TTL under load, so a legitimate slow request loses its claim and a duplicate slips through. I've seen that surface as "duplicates only happen during traffic spikes" which is the giveaway that it's a TTL-versus-latency problem, not a logic bug. The fix there is either extending the lock while the work is in flight or moving the uniqueness guarantee into the database transaction itself instead of a separate cache layer with its own expiry.

Wall clocks on different machines drift, and NTP correction can even move a clock backward. If you order ledger entries purely by timestamp, you can end up with an event that logically happened after another event showing an earlier timestamp, which is a real problem when you're trying to prove that a debit was recorded before a corresponding credit, or detecting a double-spend across regions.

A hybrid logical clock fixes this by combining the physical clock with a logical counter. Each node advances its HLC to be at least as large as the maximum of its own physical time and any timestamp it has seen in an incoming message, plus one if there's a tie. That gives you a value that's still close to real wall-clock time for humans reading logs, but is also guaranteed to respect causality: if event A caused event B, A's HLC value is always smaller than B's, no matter how the physical clocks on those two machines actually drifted.

The tradeoff against a full vector clock is that HLC gives you a single comparable value instead of a complete causal graph across every node, so it can't always tell you "these two events are concurrent" versus "one strictly preceded the other" in every case. For a payment ledger that's usually fine, because you already need a total order for settlement anyway, and HLC gives you that total order cheaply without the O(n) storage overhead per event that vector clocks require as the cluster grows.

Hot partitions usually come from picking a shard key that seemed reasonable at normal traffic but concentrates load once one entity spikes, most often merchant ID or account ID. During a flash sale, one merchant can generate ten or twenty times its normal transaction volume, and if every one of those rows hashes to the same shard, that shard's CPU and IO get saturated while every other shard sits idle, even though the cluster as a whole has plenty of capacity.

Short term, I'd add a salted suffix to the write key for known high-volume merchants, spreading their writes across several sub-partitions, then fan the reads back in with a scatter-gather query when the merchant's dashboard needs an aggregate. Longer term, I'd move to capacity planning based on scheduled events: merchants that pre-announce a big sale get bumped to a dedicated shard or a higher-throughput tier ahead of time instead of discovering the hot spot in production. I'd also put a per-merchant rate limiter at the gateway layer so one merchant's spike degrades gracefully instead of causing latency for unrelated merchants that happen to share the same physical shard.

I'd start by turning on -XX:+HeapDumpOnOutOfMemoryError if it isn't already, so the next occurrence gives me a dump to actually analyze instead of just a stack trace. Alongside that I'd pull GC logs to see whether old-gen occupancy is genuinely trending upward across full GCs, which points to a real leak, versus just growing within a normal sawtooth pattern that a bigger heap or tuned GC would handle fine.

In a transaction-processing service, the usual suspects are an unbounded cache, like a ConcurrentHashMap keyed by transaction ID that gets populated on every request but never evicted, thread-local variables that don't get cleared when a thread is returned to a pool and reused for the next request, listener or callback registrations that accumulate because something subscribes but never unsubscribes, and a queue between a producer and a slower downstream consumer that just keeps growing under sustained load. That last one is important because it looks exactly like a leak in a heap dump, lots of retained objects in a queue, but the actual fix is backpressure or a bounded queue with a rejection policy, not a code change to release references.

Once I have the dump, I'd load it into Eclipse MAT, run the dominator tree, and look for the object that's retaining the most memory relative to its own size, since that tells you what's actually holding the rest of the graph alive. Then I'd correlate the growth pattern against recent deploys and traffic shape, because "started three days ago" usually points at a specific release, while "only under sustained load" points at something that only manifests once request volume crosses a threshold, like a cache that never got an eviction policy because nobody hit that volume in staging.

Raft keeps replicas consistent by funneling all writes through a single leader, which appends the entry to its own log and replicates it to followers, and only considers the entry committed once a majority of nodes have acknowledged it. Followers never accept client writes directly, and every entry carries a term number that increases every time a new leader is elected, which is what prevents a deposed leader from coming back and committing stale writes after losing its position.

If the leader dies or gets partitioned mid-transaction, whatever it had appended to its own log but hadn't yet replicated to a majority is not committed, and it can be silently overwritten once a new leader takes over, because the new leader's log is what becomes authoritative. That means a client that sent a write to the old leader right before the failure genuinely doesn't know whether that write survived. This is exactly why the client side has to treat any write issued during a leadership transition as ambiguous and retry it with the same idempotency key rather than assuming failure and generating a new transaction.

The practical implication for ledger design is that you should never acknowledge success to the caller the moment the leader accepts the write, only once you have confirmation the entry reached the committed index with majority acknowledgment. Cutting that corner to shave latency is how you end up with a transaction that the client believes succeeded, that got rolled back invisibly during an election, and that nobody notices until reconciliation runs hours later.

Real-time scenario questions

10

Two stacks. One holds the actual values in push order. The second tracks the running minimum: push a new value onto it only when it is less than or equal to the current top, and pop from both stacks together so the minimum stack never falls out of sync with what has actually been removed.

Shows up in the Visa OA and in live rounds as a design-and-code question rather than a pure algorithm one. The standard follow-up: "can you do it with a single stack?" Yes, by storing each entry as a pair (value, minimum-so-far) instead of maintaining a second stack, which trades a bit of memory per element for one fewer data structure to manage.

java
class MinStack {
    private Deque<Integer> stack = new ArrayDeque<>();
    private Deque<Integer> minStack = new ArrayDeque<>();

    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || val <= minStack.peek()) {
            minStack.push(val);
        } else {
            minStack.push(minStack.peek());
        }
    }

    public void pop() {
        stack.pop();
        minStack.pop();
    }

    public int getMin() {
        return minStack.peek();
    }
}

Common algorithms: token bucket (allows bursting up to bucket capacity, refills at a fixed rate, good for APIs where short bursts are acceptable), fixed window counter (simple, but allows 2x the limit at window boundaries), sliding window log (accurate, high memory cost per client), sliding window counter (approximation with lower memory cost).

For Visa's context: rate limiting sits at the API gateway layer. The limit state is stored in Redis with atomic Lua scripts (to avoid race conditions when incrementing and checking the counter). For distributed rate limiting across gateway nodes, the Redis cluster is the source of truth. The follow-up at Visa: "What happens if Redis is down?" You need a circuit breaker that either fails open (allow requests through, accept burst risk) or fails closed (deny all requests, accept availability loss). Failing open is usually the right call for payment authorization.

Notifications must reach cardholders within seconds of authorization (push, SMS, email). The pipeline: the authorization service publishes an event to Kafka on every approved or declined transaction. A notification service consumes from Kafka, looks up the cardholder's registered notification preferences (from a user preferences DB with a Redis read-through cache), and dispatches to the appropriate channel via third-party providers (FCM for push, Twilio for SMS, SendGrid for email).

Key design choices: at-least-once delivery from Kafka means the notification service must be idempotent (deduplicate on transaction ID before dispatching). For international notifications, SMS routing must handle country-specific short codes and carrier delivery receipts. Reported as a system design prompt from Visa 2025 interviews on TechPrep and InterviewQuery.

The standard approach: a HashMap for O(1) key-to-node lookups, combined with a doubly linked list to track access order. The most recently used node sits at the list head, the least recently used at the tail. On get, move the node to the head. On put that exceeds capacity, evict the tail node and remove its key from the map.

Reported as a Visa technical round question that specifically asked for pseudocode first, then time and space complexity analysis, then implementation. The interviewer then extended it: "How would you adapt this for a distributed payment authorization cache across multiple gateway pods?" The answer involves a consistent hashing ring to distribute keys across Redis nodes, so a given card's cache entry always lands on the same shard.

Every authorization needs an ID that is unique across every gateway node, roughly sortable by time for debugging and indexing, and generated without a round trip to a shared database, since that database becomes the bottleneck the moment volume climbs into the millions of transactions per second. A Snowflake-style scheme covers all three: pack a millisecond timestamp, a machine or node identifier, and a per-millisecond sequence counter into a single 64-bit integer. Each node hands out its own IDs with zero coordination at generation time.

The node ID gets assigned once at boot, either from static config or a coordination service. The sharp edge Visa interviewers ask about: what happens when a machine's clock jumps backward after an NTP correction? A generator that ignores this can emit a duplicate ID. The safe answer is to detect the backward jump and either block briefly until the clock catches back up or refuse to issue IDs until it does, rather than silently reusing a timestamp.

java
// Simplified Snowflake-style ID: 41 bits timestamp, 10 bits node, 12 bits sequence
public synchronized long nextId() {
    long timestamp = System.currentTimeMillis() - EPOCH;
    if (timestamp < lastTimestamp) {
        throw new IllegalStateException("Clock moved backwards, refusing to generate id");
    }
    if (timestamp == lastTimestamp) {
        sequence = (sequence + 1) & 0xFFF;         // 12-bit sequence
        if (sequence == 0) timestamp = waitNextMillis(lastTimestamp);
    } else {
        sequence = 0;
    }
    lastTimestamp = timestamp;
    return (timestamp << 22) | (nodeId << 12) | sequence;
}

Start with the core flow: merchant initiates, gateway validates the request (format, authentication), routes to the card network (Visa, in this case), authorization response comes back, gateway returns the result to the merchant. Idempotency is the first hard requirement: if the network drops the request mid-flight, the merchant will retry. The gateway must deduplicate retries by keying on a merchant-provided idempotency key and storing its state in a Redis cluster with a short TTL.

For scale: horizontal stateless gateway pods behind a load balancer, with sticky sessions only at the authentication layer. Database writes (transaction records) go through an async write queue (Kafka) to decouple authorization latency from persistence latency. Read the authorization status from a fast cache (Redis) rather than hitting the DB on every status check. Reported as a senior-level Visa system design prompt in 2025.

python
# Idempotency key check pattern (simplified)
def process_payment(idempotency_key: str, payload: dict) -> dict:
    # Check Redis for existing result
    cached = redis.get(f"txn:{idempotency_key}")
    if cached:
        return json.loads(cached)  # Return previous result, no duplicate

    # Process the transaction
    result = authorize_with_network(payload)

    # Store result with TTL (e.g., 24 hours)
    redis.setex(
        f"txn:{idempotency_key}",
        86400,
        json.dumps(result)
    )
    return result

Every authorization request must be scored in under 100ms (the industry standard for card authorization response time). That constraint rules out any synchronous ML model inference that requires a database round-trip for historical features. The architecture has two layers: a real-time scoring layer (sub-100ms, rule engine plus a pre-computed feature store) and an offline enrichment layer (streaming pipeline that updates fraud signals and retrains models).

Real-time features live in Redis (velocity counts per card per window, known device fingerprint, merchant category). The ML model scores against those pre-computed features, no joins allowed in the hot path. Transactions that score above a threshold are sent to a Kafka topic for human review. The offline pipeline (Flink or Spark Streaming) continuously updates the feature store from settled transaction data. Reported as a system design prompt in a Visa senior SWE interview in 2025.

Active-active across at least two regions, not active-passive. Each region can authorize transactions on its own; a standby that only comes alive after a failover adds minutes of downtime the payments network cannot absorb. Traffic routing sits above the regions (Anycast or a health-checked global load balancer), so a failing region simply stops receiving new requests without anyone manually cutting over.

The harder part is data. Synchronous cross-region replication means no transaction is lost, but every authorization now waits on a round trip to a second data center, which is not acceptable at sub-100ms targets. Visa's actual trade-off leans the other way: replicate asynchronously, accept that a handful of in-flight authorizations at the moment of failure might need reconciliation, and lean on the settlement layer to catch anything that slipped through. An authorization is reversible; a lost ledger entry is not, which is why the two layers get different consistency guarantees. Reported in senior-level Visa design rounds in 2025 and 2026, usually as a follow-up to the payment gateway question once the interviewer wants to see how a candidate reasons about failure, not just happy-path scale.

Model the dispute as a state machine, not a single record. A case moves through stages like retrieval request, chargeback, representment, pre-arbitration, and arbitration, and each stage has its own deadline (often 20 to 45 days depending on the reason code) and its own set of documents attached. I'd back this with an event-sourced store where every transition is an immutable event, because a good chunk of these cases eventually get pulled into legal arbitration and you need a defensible audit trail, not just a "status" column that got overwritten.

The hard part isn't the state machine, it's that issuer, acquirer, and merchant are separate organizations exchanging data over batch files, async APIs, or a shared dispute network, and each side references the case by a slightly different identifier (case ID versus retrieval reference number versus original transaction ID). You need a reconciliation job that maps all of those identifiers back to one canonical case, and it has to be idempotent, because file re-uploads and retried API calls will duplicate events if you're not careful. I'd also put a hard SLA-tracking layer on top that escalates cases nearing their response deadline, since missing a deadline in this workflow usually means an automatic loss for whichever party failed to respond.

Envelope encryption is what makes this tractable. Each record is encrypted with a data encryption key, and that DEK is itself encrypted by a key encryption key held in an HSM or KMS. Rotating the KEK is cheap: you just re-wrap the small DEKs with the new KEK, you never touch the bulk card data at all, and that operation can complete in seconds even across a huge dataset.

Rotating the DEK is the harder case because that actually does require re-encrypting the underlying data eventually. The way to do it without a big-bang migration is to version the DEKs, tag every encrypted record with the DEK version used to write it, and keep older DEK versions available for decryption instead of deleting them immediately. New writes always use the newest DEK version. Existing records get lazily re-encrypted to the current version on their next read-modify-write, or by a throttled background job that walks the table during off-peak hours so it doesn't compete with production traffic.

PCI DSS also expects separation of duties around this, so the people who can trigger a rotation shouldn't be the same people who can access raw key material, and every key use should be logged for audit. The failure mode I'd guard against is deleting an old key version too early, which instantly makes every record still on that version unreadable, so the retirement of an old DEK version should be gated on confirming zero records still reference it.

What we've seen across Visa loops

Candidates who practice Visa loops on LastRoundAI show a pattern that prep guides rarely flag: the CS fundamentals section surprises them more than the coding section. Most candidates spend 80% of their prep time on LeetCode and hit the OA cleanly, then stall when the interviewer pivots to "explain deadlock conditions" or "what does a TLB do." At Visa, these are not warm-up questions. They count.

The second pattern: Visa interviewers ask "why Visa?" and specifically probe whether you know what the authorization flow actually looks like. Candidates who answer with "I'm excited to work on payments at scale" without knowing that authorization involves the issuing bank, the card network, and the acquiring bank, in that order, leave the interviewer unimpressed. Spend 20 minutes reading Visa's published developer documentation on how VisaNet routes transactions before your loop.

For system design: the payments domain context is not decoration. "Design a rate limiter" at Visa means you should bring up card-testing fraud and why the token bucket allows controlled bursting without mentioning that application to the problem is a significant gap. The candidates who pass the design round consistently connect their architecture choices to the payments context without being prompted.

Leave a Reply

Your email address will not be published. Required fields are marked *