Mastercard Interview Questions · 2026

Mastercard Interview Questions (2026): Real, With Answers

A candidate who interviewed for a Software Engineer II role at Mastercard described the loop on LinkedIn as three technical rounds, coding, core Java and Spring, then a system design conversation about scaling a service to millions of users, followed by a values-focused final round. That structure shows up again and again across candidate reports on Glassdoor, where Mastercard's Software Engineer interviews carry a 3-out-of-5 difficulty rating and a 64 percent positive experience score across roughly 175 reviews (Glassdoor, Mastercard Software Engineer Interviews). The average time to an offer is about 24 to 32 days depending on the source, which is longer than a lot of fintechs but shorter than most big banks.

What makes Mastercard's loop distinct isn't the coding round, it's the behavioral one. Mastercard scores every candidate on what it calls a Decency Quotient, or DQ, a values assessment that shows up at every level from new grad to staff engineer, sitting alongside the usual STAR-format questions about leadership, failure, and conflict. Candidates who treat the final round as a formality tend to get tripped up here, because interviewers are listening for empathy and ethical reasoning specifically, not just a clean example structure.

This page groups roughly 50 questions the way candidates actually report Mastercard's rounds: an online coding assessment on HackerRank, core Java and Spring fundamentals, a live DSA round, system design for senior and staff candidates, SQL and data questions for analyst and data engineering tracks, frontend and React questions for full-stack and digital roles, the DQ-flavored behavioral round, and a closing HR conversation. Not every candidate sees every round, entry-level loops skip system design entirely, and data roles skip most of the Java section, so skim to the section that matches the role you're prepping for.

~50Questions
3-4Rounds
HackerRankOA Platform
Decency Quotient (DQ)Values Screen

Online assessment: the HackerRank coding round

Most Mastercard software roles, new grad through mid-level, open with a timed HackerRank assessment. Interns report two problems in 45 minutes with a bonus third question if time allows; full-time candidates more often see two problems rated easy-to-medium, frequently touching array manipulation or string processing with a twist rather than a textbook version of the problem.

Easy questions

17

This is the one candidates specifically report from Mastercard's on-campus SDE loop, a sequence like [1,2,3,5] where 4 is missing. The naive answer is a linear scan comparing each element to its expected value, which works and is O(n), but the interviewer is really checking whether you reach for the arithmetic shortcut instead.

java
int findMissing(int[] arr) {
  int n = arr.length + 1;
  long expectedSum = (long) n * (arr[0] + arr[arr.length - 1] + n - 1) / 2;
  // simpler: sum of 1..n using first value as the offset
  long actualSum = 0;
  for (int x : arr) actualSum += x;
  long expected = 0;
  for (int i = 0; i < n; i++) expected += arr[0] + i;
  return (int) (expected - actualSum);
}

Say out loud that this only works cleanly when you know the array is contiguous and sorted. If the array can have gaps in the middle or isn't sorted, you're back to a hash set of expected values, which is still O(n) but for a different reason.

Track two running values in a single pass: the largest seen so far and the second largest. Every time a new element beats the current largest, the old largest becomes the new second largest before you overwrite it. Every time an element beats only the second largest, update just that one.

java
int secondLargest(int[] nums) {
  int first = Integer.MIN_VALUE, second = Integer.MIN_VALUE;
  for (int n : nums) {
    if (n > first) {
      second = first;
      first = n;
    } else if (n > second && n != first) {
      second = n;
    }
  }
  return second;
}

The edge case interviewers actually probe here is duplicates. If the array is [5, 5, 3], is the second largest 5 or 3? Ask which convention they want before you commit to the != first check above.

A single pass with a hash set beats the nested-loop version every time this comes up. For each number, check whether target minus that number is already in the set before adding the current number in.

java
boolean hasPairWithSum(int[] nums, int target) {
  Set<Integer> seen = new HashSet<>();
  for (int n : nums) {
    if (seen.contains(target - n)) return true;
    seen.add(n);
  }
  return false;
}

O(n) time, O(n) space. If the interviewer follows up asking for O(1) extra space, that's your cue that the array is sorted or can be sorted, and a two-pointer approach from both ends replaces the hash set.

Print Fizz for multiples of three, Buzz for multiples of five, FizzBuzz for both, the number otherwise. Entry-level Mastercard loops still use this as a first filter, and candidates who've reported seeing it describe it as a five-minute warm-up before the real questions start.

java
for (int i = 1; i <= 100; i++) {
  if (i % 15 == 0) System.out.println("FizzBuzz");
  else if (i % 3 == 0) System.out.println("Fizz");
  else if (i % 5 == 0) System.out.println("Buzz");
  else System.out.println(i);
}

It's a filter, not a real signal. Companies keep it because a surprising number of candidates who list years of experience still stumble on the modulo logic or the ordering of the conditions, and that tells the interviewer more about how carefully someone reads a spec than the problem itself would suggest.

An abstract class can hold state, constructors, and a mix of implemented and unimplemented methods, but a class can only extend one. An interface (since Java 8) can have default methods too, but it still can't hold instance fields with state the way an abstract class can, and a class can implement as many interfaces as it needs.

Reach for an abstract class when subclasses genuinely share implementation and state, a base Vehicle class with a shared speed field. Reach for an interface when you're defining a capability multiple, otherwise unrelated classes can implement, Comparable, Runnable, that kind of contract.

== compares references for objects, whether two variables point at the exact same memory location, while.equals() compares logical equality, which for a class like String or a custom object means whatever the equals() override says it means. Two different String objects with identical characters are == false but.equals() true.

java
String a = new String("card");
String b = new String("card");
System.out.println(a == b);    // false, different objects
System.out.println(a.equals(b)); // true, same content

The follow-up worth knowing: string literals get interned, so "card" == "card" is often true, which is exactly why relying on == for strings is a trap.

TLS encrypts data in transit and authenticates the server (and optionally the client) so a request between two parties can't be read or silently altered by a machine sitting in the middle. Any API cares about this, but a payment API's data, card numbers, transaction amounts, account identifiers, is directly monetizable if intercepted, which is why payment networks mandate TLS 1.2 or higher and often mutual TLS for server-to-server calls, not just a plain HTTPS certificate on the edge.

Iteratively, walk the list once, flipping each node's next pointer to point backward, tracking the previous node as you go.

java
ListNode reverse(ListNode head) {
  ListNode prev = null;
  while (head != null) {
    ListNode next = head.next;
    head.next = prev;
    prev = head;
    head = next;
  }
  return prev;
}

The recursive version reverses the rest of the list first, then fixes up the current node's pointers on the way back up the call stack. It's elegant but costs O(n) stack space, which matters if the list could be long enough to blow the stack, something worth saying out loud even if you write the recursive version.

Floyd's cycle detection, two pointers moving at different speeds through the same list. If there's a cycle, the fast pointer eventually laps the slow one and they meet. If there's no cycle, the fast pointer reaches null first.

java
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;
}

O(1) space beats the hash-set alternative, which also works but costs O(n) memory tracking visited nodes.

Unsorted, a hash map from value to index gets you O(n) time: for each number, check if target minus that number is already in the map before adding the current number.

java
int[] twoSum(int[] nums, int target) {
  Map<Integer, Integer> seen = new HashMap<>();
  for (int i = 0; i < nums.length; i++) {
    int complement = target - nums[i];
    if (seen.containsKey(complement)) {
      return new int[]{seen.get(complement), i};
    }
    seen.put(nums[i], i);
  }
  throw new IllegalArgumentException("No solution");
}

Sorted, drop the hash map for two pointers from both ends, which cuts space to O(1) at the cost of losing the original indices unless you tracked them before sorting.

sql
SELECT
  customer_id,
  DATE_TRUNC('quarter', transaction_date) AS quarter,
  SUM(amount) AS total_amount
FROM transactions
GROUP BY customer_id, DATE_TRUNC('quarter', transaction_date)
ORDER BY customer_id, quarter;

The interviewer usually follows up asking how you'd handle a customer with zero transactions in a given quarter, which pushes you toward a generated calendar table joined with a LEFT JOIN so quarters with no activity still show up as zero instead of disappearing entirely.

LEFT JOIN keeps every row from the left table, filling in NULLs for any right-table columns that don't have a match. RIGHT JOIN does the mirror image, keeping every row from the right table. For any query, you can rewrite a RIGHT JOIN as a LEFT JOIN by swapping the table order, so the two are equivalent in expressive power; RIGHT JOIN just isn't used much because writing the tables in LEFT-JOIN order reads more naturally to most people.

Props are read-only data passed into a component from its parent, the component receiving them can't modify them directly. State is data a component owns and manages internally, and changing it triggers a re-render. A component that only receives props and never manages its own state is a good candidate for a pure functional component with no local state at all.

Class components manage state through this.state and this.setState and hook into lifecycle through named methods like componentDidMount. Functional components use hooks, useState, useEffect, and so on, to get the same capabilities without a class or the this-binding headaches that come with it. Hooks also make it much easier to extract and reuse stateful logic across components through custom hooks, which classes never had a clean equivalent for.

I wouldn't start a new component as a class in 2026. The one legitimate reason to still write one is an error boundary, since React doesn't yet offer a hook-based equivalent for catching render errors in a subtree; that's genuinely still a class-only feature.

Give a specific, honest self-assessment rather than a list of generic strengths. Something like "I do my best thinking out loud, so I tend to talk through a problem in a shared doc or a quick call rather than disappearing to think alone, and I've learned to warn new teammates about that upfront so it doesn't read as me not having a plan yet." Concrete, slightly self-aware answers land better here than a rehearsed strengths list.

A generic "I like payments technology" answer doesn't differentiate you from every other candidate in the pipeline. Anchor your answer in something specific: a product area you find genuinely interesting, a scale problem (billions of transactions across a global network) you want direct exposure to, or something concrete about the team or role you learned during earlier rounds. Naming a specific detail from an earlier conversation with the interviewer or recruiter shows you were actually listening, not reciting a template.

This is a genuine get-to-know-you question, not a trick, but a flat one-line answer wastes it. Pick something you can talk about with real specificity for a minute or two, and if you can tie it back to a trait relevant to the job, organizing a community event showing project-coordination skills, say, that's a bonus, not a requirement. Forcing the connection when there isn't a natural one usually reads as more contrived than just answering the question honestly.

Medium questions

26

This is the string-manipulation problem candidates describe as "complex logic" from Mastercard's intern OA, and it's a sliding window underneath. Keep a window defined by two pointers and a set (or a map of character to last-seen index) tracking what's currently inside the window. When you hit a repeat, shrink the window from the left until the repeat is gone.

java
int longestUniqueSubstring(String s) {
  Map<Character, Integer> lastIndex = new HashMap<>();
  int maxLen = 0, left = 0;
  for (int right = 0; right < s.length(); right++) {
    char c = s.charAt(right);
    if (lastIndex.containsKey(c) && lastIndex.get(c) >= left) {
      left = lastIndex.get(c) + 1;
    }
    lastIndex.put(c, right);
    maxLen = Math.max(maxLen, right - left + 1);
  }
  return maxLen;
}

Stream the file line by line rather than reading it into a list. In Java, a BufferedReader combined with a fixed-size batch buffer, say 1,000 records at a time, lets you validate and write out results in chunks, so peak memory stays flat regardless of file size.

java
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
  List<Record> batch = new ArrayList<>(1000);
  String line;
  while ((line = reader.readLine()) != null) {
    batch.add(parse(line));
    if (batch.size() == 1000) {
      processBatch(batch);
      batch.clear();
    }
  }
  if (!batch.isEmpty()) processBatch(batch);
}

If the interviewer pushes further, the next layer is parallelizing across batches with a thread pool and making each batch's processing idempotent, since a retry after a partial failure shouldn't double-process a record.

This scenario-style question shows up in Mastercard's first technical round. Shuffle the question list with Collections.shuffle, then split it into three roughly equal chunks, ten questions across three people means sizes of 4, 3, 3, using index math rather than trying to divide evenly.

java
List<Integer> questions = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
Collections.shuffle(questions);
int perPerson = questions.size() / 3;
int remainder = questions.size() % 3;
int idx = 0;
for (int person = 0; person < 3; person++) {
  int count = perPerson + (person < remainder ? 1 : 0);
  List<Integer> assigned = questions.subList(idx, idx + count);
  System.out.println("Person " + person + ": " + assigned);
  idx += count;
}

What the interviewer wants to hear is the fairness reasoning, that you shuffled before splitting so no one systematically gets easier or harder questions by index position, not just that the code compiles.

Java 8 introduced the Stream API itself, map, filter, collect, and lazy evaluation over collections. By Java 21, the additions worth mentioning are Stream.toList() as a shorthand for collect(Collectors.toList()), and, more broadly, records and pattern matching for switch, which change how you'd model the data flowing through a stream even if the stream operations themselves haven't changed much.

java
// Java 8 style
List<String> names = users.stream()
 .map(User::getName)
 .collect(Collectors.toList());

// Java 16+ shorthand
List<String> names2 = users.stream().map(User::getName).toList();

Candidates who've only worked in a Java 8 codebase can usually still answer this fine conceptually. What trips people up is being asked to name a specific version number for a specific feature, so it's fine to say "I know the shorthand exists, I'd have to check which LTS version introduced it" rather than guessing confidently and getting it wrong.

SAML is XML-based and built for browser-driven single sign-on into web applications, an identity provider issues a signed assertion the service provider trusts. OAuth 2.0 is a token-based authorization framework, not strictly authentication, built around granting a client limited access to a resource on a user's behalf without handing over the user's password.

OIDC, OpenID Connect, layers an identity token on top of OAuth 2.0 specifically to add the authentication piece OAuth alone doesn't guarantee. In practice, a payments company's internal enterprise apps often still lean on SAML for workforce SSO, while customer-facing APIs and mobile apps almost always use OAuth 2.0 or OIDC, because SAML's XML overhead is a poor fit for mobile clients.

At minimum, require a valid bearer token on every request and validate it against an identity provider or a shared secret, then restrict the endpoint to a specific role or scope rather than just "logged in." Spring Security's filter chain is the place to enforce this, before the request ever reaches your controller.

java
@Configuration
public class SecurityConfig {
  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
       .requestMatchers("/internal/**").hasRole("SERVICE")
       .anyRequest().authenticated())
     .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
  }
}

For service-to-service calls specifically, client credentials grant is the usual OAuth flow, since there's no human in the loop to redirect for a login screen.

Single responsibility, a class should have one reason to change. Open/closed, open for extension, closed for modification. Liskov substitution, a subtype should be usable anywhere its parent type is expected without breaking behavior. Interface segregation, don't force a class to implement methods it doesn't need. Dependency inversion, depend on abstractions, not concrete implementations.

My honest answer on which gets violated most: single responsibility, by a wide margin. Services start focused and accumulate one more method, one more side effect, one more reason to change, until a "UserService" is quietly handling authentication, email notifications, and audit logging. Nobody sets out to violate SRP; it happens one small addition at a time.

A tree is height-balanced if, for every node, the height difference between its left and right subtrees is at most one. The efficient approach computes height and checks balance in the same recursive pass, returning a sentinel value like -1 the moment an imbalance is found anywhere below, so the check short-circuits instead of recomputing height from scratch at every node.

java
boolean isBalanced(TreeNode root) {
  return height(root) != -1;
}

int height(TreeNode node) {
  if (node == null) return 0;
  int left = height(node.left);
  if (left == -1) return -1;
  int right = height(node.right);
  if (right == -1) return -1;
  if (Math.abs(left - right) > 1) return -1;
  return Math.max(left, right) + 1;
}

The naive version, computing height separately at every node, works but is O(n log n) in the worst case instead of O(n). Interviewers notice which version you reach for first.

Kadane's algorithm, tracking the best sum ending at the current position and the best sum overall in a single pass. At each element, decide whether extending the previous subarray or starting fresh at the current element gives a bigger sum.

java
int maxSubarraySum(int[] nums) {
  int currentMax = nums[0], globalMax = nums[0];
  for (int i = 1; i < nums.length; i++) {
    currentMax = Math.max(nums[i], currentMax + nums[i]);
    globalMax = Math.max(globalMax, currentMax);
  }
  return globalMax;
}

A payments-flavored framing of this same question: "find the best consecutive-day window of net positive revenue," which is the identical algorithm with a different story attached.

A sliding window backed by a queue (or deque) of timestamps. On each new event, push the timestamp, then pop timestamps from the front that fall outside the 60-second window. The queue's size at any moment is the answer.

java
class RecentCounter {
  Deque<Integer> window = new ArrayDeque<>();

  int ping(int t) {
    window.addLast(t);
    while (window.peekFirst() < t - 60000) {
      window.pollFirst();
    }
    return window.size();
  }
}

This is a fraud-detection-adjacent question in disguise, rate limiting and anomaly windows both reduce to the same sliding-window structure.

Merge from the back, not the front. If array A has enough trailing empty space to hold both, compare the largest remaining elements of A and B and place the bigger one at the end, working backward. This avoids overwriting elements of A you still need to compare.

java
void merge(int[] a, int m, int[] b, int n) {
  int i = m - 1, j = n - 1, k = m + n - 1;
  while (j >= 0) {
    if (i >= 0 && a[i] > b[j]) {
      a[k--] = a[i--];
    } else {
      a[k--] = b[j--];
    }
  }
}

Neither table gets a foreign key to the other directly, since either side could have multiple matches. You add a join table, BookAuthor, with two foreign keys, book_id and author_id, and the join table's primary key is usually the composite of both, which also naturally prevents the same book-author pairing from being inserted twice.

sql
CREATE TABLE book_author (
  book_id INT REFERENCES book(id),
  author_id INT REFERENCES author(id),
  PRIMARY KEY (book_id, author_id)
);

This exact question came up in a Mastercard on-campus loop as a whiteboard-style modeling exercise, not a coding one. The interviewer is checking whether you reach for a join table automatically or start second-guessing yourself into a denormalized design.

Vertical scaling means giving a single machine more resources, more CPU, more RAM. It's simple but hits a hard ceiling and creates a single point of failure. Horizontal scaling means adding more machines and distributing load across them, which scales further but adds coordination complexity, load balancing, data consistency across nodes.

Sharding is a specific horizontal-scaling technique for databases: splitting data across multiple database instances based on a shard key, customer ID or transaction region, say, so each shard only holds a fraction of the total data and handles a fraction of the total query load. The tradeoff is that queries spanning multiple shards, "give me all transactions across every customer this month," get considerably harder to write and slower to run.

ACID, atomicity, consistency, isolation, durability, guarantees a transaction either fully happens or doesn't happen at all, and every reader sees a consistent view of the data. BASE, basically available, soft state, eventually consistent, trades strict consistency for availability and partition tolerance, accepting that a reader might briefly see stale data.

A transaction ledger needs ACID, full stop. A payment either debited an account or it didn't; there's no acceptable "eventually" for that fact. A recommendation feed or a view counter can live comfortably on BASE, since a slightly stale recommendation list costs nothing and the system stays responsive even under heavy load or a network partition.

A circuit breaker. Track the failure rate of calls to the downstream service, and once it crosses a threshold, stop sending real requests for a cooldown period and fail fast (or serve a fallback) instead. That protects your own service's threads and latency from being consumed waiting on a dependency that's already struggling, and it gives the downstream service room to recover instead of getting hammered by retries from every caller simultaneously.

A self-join on the employee table, joining each row to its manager's row through the manager_id foreign key, then filtering where the employee's salary exceeds the manager's.

sql
SELECT e.name AS employee, e.salary, m.name AS manager, m.salary AS manager_salary
FROM employee e
JOIN employee m ON e.manager_id = m.id
WHERE e.salary > m.salary;

This is a classic self-join test, and the thing interviewers watch for is whether you reach for a self-join naturally or start trying to write two separate queries and stitch the results together in application code.

sql
SELECT
  customer_id,
  region,
  total_spend,
  RANK() OVER (PARTITION BY region ORDER BY total_spend DESC) AS spend_rank
FROM (
  SELECT customer_id, region, SUM(amount) AS total_spend
  FROM transactions
  GROUP BY customer_id, region
) t;

RANK() leaves gaps after ties (1, 1, 3), DENSE_RANK() doesn't (1, 1, 2). Which one is "correct" depends entirely on what the business question actually is, and a good candidate asks which behavior is wanted rather than picking one silently.

An index speeds up lookups on a specific column or set of columns by maintaining a sorted structure the query planner can use instead of scanning every row. Partitioning splits a table's physical storage into separate chunks, often by date range or a key like region, so queries that only need one partition can skip scanning the rest of the table entirely.

They solve different problems and stack well together: partition a large transactions table by month so queries scoped to a recent date range only touch one or two partitions, then index within each partition on customer_id for fast per-customer lookups. Partitioning without indexing still leaves you scanning a whole partition; indexing without partitioning still means the index itself grows unbounded as the table grows.

The real DOM is the browser's actual rendered tree, and directly manipulating it, especially repeatedly, is expensive because every change can trigger layout recalculation and repainting. The virtual DOM is a lightweight in-memory representation React keeps in sync with your component tree; when state changes, React builds a new virtual DOM tree, diffs it against the previous one, and applies only the minimal set of real DOM updates needed, rather than re-rendering everything from scratch.

useEffect runs a side effect after render, and the dependency array controls when it re-runs: an empty array means run once on mount, no array means run after every render, and a list of values means re-run only when one of those values changes between renders.

typescript
useEffect(() => {
 fetchTransactions(customerId).then(setTransactions);
}, [customerId]); // re-fetches only when customerId changes

The mistake I see most: an object or array created fresh on every render passed into the dependency array. Since it's a new reference every time even if the contents look identical, React sees it as "changed" on every single render and the effect fires in an infinite loop. The fix is usually memoizing that value with useMemo or moving its creation outside the component.

Nested routes let a parent route render shared layout, a header or sidebar, while its child routes render inside an <Outlet />, so you're not duplicating that layout markup on every page. For an authenticated route, wrap it in a component that checks auth state and either renders the protected route's children or redirects to a login page.

typescript
function ProtectedRoute({ children }: { children: JSX.Element }) {
 const { isAuthenticated } = useAuth();
 return isAuthenticated ? children : <Navigate to="/login" />;
}

Pick a specific moment where a gap existed and nobody had explicitly assigned you to fill it, a project stalling because no one owned the cross-team coordination, a production incident where the on-call engineer needed help interpreting an unfamiliar system. Describe the decision to step in, what you actually did, and the outcome, but spend real time on why you chose to act rather than wait for someone else to.

The DQ angle here specifically rewards answers where you also credit or bring others along, rather than a lone-hero framing where you solved everything single-handedly while everyone else stood around.

Pick a real failure with real stakes, not a humblebrag disguised as a weakness. Walk through the decision that went wrong, what you missed at the time, and specifically what you changed afterward, a process you now follow, a check you now run, a conversation you now have earlier. The failure itself matters less than whether you can name a concrete, lasting change it produced in how you work.

Focus less on how difficult the person was and more on what you did to understand what was actually driving their behavior, a deadline pressure they were under, a past bad experience with your team, a misunderstanding about scope. Describe the specific action you took to bridge that gap, and be honest if the relationship only partially improved; a tidy, fully-resolved ending can read as less credible than an honest, still-imperfect one.

Interviewers are listening for whether you treated the pivot as a data point to act on quickly or as a personal setback to resist. Walk through what the original plan was, what specifically changed your mind, whether it was a customer complaint, a stakeholder's new priority, or your own testing surfacing a flaw, and how fast you actually moved once you had that information.

This is checking for genuine curiosity about the broader business, not just the role description. A strong answer names an adjacent area you'd want exposure to, mentoring, a specific technical domain within payments, cross-team collaboration, and explains briefly why, rather than answering purely in terms of promotion or title. Interviewers are listening for whether you think about impact beyond your own ticket queue.

Hard questions

5

synchronized is built into the language, ties the lock to a specific object monitor, and releases automatically when the block exits, even on an exception. ReentrantLock from java.util.concurrent.locks gives you more control: tryLock with a timeout instead of blocking forever, fairness ordering between waiting threads, and the ability to lock and unlock across method boundaries, but you're responsible for releasing it yourself, usually in a finally block.

java
private final ReentrantLock lock = new ReentrantLock();

void transfer(Account from, Account to, int amount) {
  lock.lock();
  try {
    from.debit(amount);
    to.credit(amount);
  } finally {
    lock.unlock();
  }
}

I'd default to synchronized for straightforward mutual exclusion and only reach for ReentrantLock when I specifically need tryLock, fairness, or a lock held across more than one method. Adding complexity you don't need is its own kind of bug.

The strangler fig pattern is the standard answer: put a routing layer in front of the monolith, then peel off one bounded piece of functionality at a time into its own service, routing an increasing share of traffic to the new service while the monolith still handles everything else. You never do a big-bang cutover.

Pick the first piece to extract based on what's least entangled with the rest of the system, usually something read-heavy and stateless, a transaction-status lookup endpoint, rather than the core ledger-writing path, which touches everything else and is the last thing you'd want to be the guinea pig for a new deployment pattern.

A hot partition usually means the shard key has low cardinality or a skewed access pattern, everything is landing on the same few shards because the key doesn't spread writes evenly. A common mistake is sharding by something like transaction date, which concentrates all of today's writes onto one shard no matter how many shards exist.

The fix is a composite or higher-cardinality shard key, something like a hash of customer_id combined with date, which spreads writes across shards evenly while still letting you query efficiently within a customer's history. Cassandra and similar NoSQL stores are explicitly built for this kind of horizontal write scaling, but they're only as good as the shard key you hand them.

The stack holds fixed-size, short-lived data, function call frames and primitive local variables, and gets cleaned up automatically as functions return. The heap holds dynamically-sized data, objects and closures, and lives until nothing references it anymore, at which point garbage collection reclaims it.

A closure keeps variables from its enclosing scope alive on the heap even after the outer function has returned, specifically because something still holds a reference to the inner function. That's the mechanism behind a React custom hook's state persisting correctly across renders, and it's also the classic cause of a memory leak if a closure captures a large object and gets attached to something long-lived, like a global event listener that's never removed.

This one is close to the center of what DQ is actually testing for, so a vague or hypothetical answer stands out immediately. Use a real, specific situation, flagging a bug that would have delayed a launch you knew you'd be blamed for, pushing back on a shortcut a manager wanted that would have misrepresented test coverage, and be honest about what it actually cost you, a missed deadline, an uncomfortable conversation, being overruled anyway.

The interviewer isn't grading whether the story has a clean happy ending. They're grading whether you can recognize an ethical tradeoff in the moment and choose the harder, more honest path even when it isn't free.

Real-time scenario questions

5

Back it with an ArrayList for O(1) random access by index, plus a HashMap from value to its index in that list. Insert appends to the list and records the index. Delete is the tricky part: swap the element being removed with the last element in the list, update the swapped element's index in the map, then remove the last slot, which avoids the O(n) shift a normal middle-of-list removal would cost.

java
class RandomizedSet {
  List<Integer> values = new ArrayList<>();
  Map<Integer, Integer> indexOf = new HashMap<>();
  Random rand = new Random();

  boolean insert(int val) {
    if (indexOf.containsKey(val)) return false;
    indexOf.put(val, values.size());
    values.add(val);
    return true;
  }

  boolean remove(int val) {
    if (!indexOf.containsKey(val)) return false;
    int idx = indexOf.get(val);
    int lastVal = values.get(values.size() - 1);
    values.set(idx, lastVal);
    indexOf.put(lastVal, idx);
    values.remove(values.size() - 1);
    indexOf.remove(val);
    return true;
  }

  int getRandom() {
    return values.get(rand.nextInt(values.size()));
  }
}

The swap-with-last trick is the whole answer here. Candidates who reach for a LinkedList or try to keep the array in insertion order almost always end up with an O(n) delete without noticing.

Start by splitting the batch into independent chunks that can be processed in parallel, a queue-based worker pool rather than a single sequential pass. Each worker pulls a chunk, validates it, writes results, and acknowledges completion, so a crashed worker only loses its current chunk, not the whole batch.

Reliability comes from making each chunk's processing idempotent, tagging each record with a unique identifier and checking a processed-records table before applying it, so a retry after a partial failure doesn't double-charge or double-count anything. For speed, horizontal scaling of workers plus batched database writes (inserting 500 rows at once instead of one row per statement) usually gets you most of the throughput gain without adding real architectural complexity. I'd only reach for a fully distributed processing framework like Spark once a single beefy queue-and-worker setup demonstrably can't keep up.

The core tension is latency versus accuracy: a fraud check has to return a decision in well under a second at authorization time, which rules out anything that needs to scan historical data from cold storage. The practical answer splits scoring into two tiers. A fast, in-memory rules and lightweight-model layer runs synchronously at authorization time using recent, cached features (velocity of transactions in the last few minutes, merchant category, geographic distance from the last transaction). A heavier, asynchronous model reprocesses the transaction after the fact for pattern detection that feeds back into future scoring.

Feature freshness is the hard part. Something like "number of transactions on this card in the last 5 minutes" needs a stream-processing layer, Kafka feeding a windowed aggregation, not a query against the primary transactional database, because that database is busy handling the actual transaction volume and can't also serve as your real-time feature store without adding latency to every single card swipe.

Require the client to send a unique idempotency key with every payment request, generated once per logical transaction attempt, not regenerated on retry. The server stores that key alongside the result of the first successful processing attempt. When a request arrives with a key that's already been seen, the server returns the stored result immediately instead of reprocessing.

java
@PostMapping("/payments")
ResponseEntity<PaymentResult> charge(@RequestHeader("Idempotency-Key") String key,
                   @RequestBody PaymentRequest req) {
  Optional<PaymentResult> existing = idempotencyStore.get(key);
  if (existing.isPresent()) return ResponseEntity.ok(existing.get());

  PaymentResult result = paymentService.process(req);
  idempotencyStore.save(key, result);
  return ResponseEntity.ok(result);
}

The subtlety worth mentioning unprompted: the store-and-check has to be atomic, a compare-and-set or a unique database constraint on the key, not a separate read followed by a separate write, or two near-simultaneous retries can both slip through the check before either one saves its result.

Reconciliation is fundamentally a diffing problem between two datasets that should agree but are produced by different systems on different schedules, the authorization log written in real time and the settlement file that typically lands in a batch hours or a day later. The design needs a matching key that both sides can compute independently, usually a transaction ID plus amount plus merchant, and a scheduled job that runs the diff once settlement data lands.

Three buckets fall out of that diff: authorized-and-settled (the happy path, no action), authorized-but-not-yet-settled (fine if within an expected window, flagged if it's been too long), and settled-with-no-matching-authorization (which should never happen and needs an alert, not a silent log line, since it usually points at either a data-integrity bug or something worse).

How to prepare for a Mastercard interview

Given how consistently candidates describe the same three-to-four round shape, coding, core Java or SQL, system design or a project deep-dive, then a values-driven behavioral round, prep time is best split unevenly rather than spread thin. Spend real time on two to three concrete STAR stories built specifically around ethics, ownership, and how you've treated people under pressure, since the DQ round rewards specificity over polish. For the technical side, refresh Java fundamentals and Spring basics if you're on a backend track, or React fundamentals and a resume-ready project walkthrough if you're on a full-stack or digital track, rather than grinding generic algorithm problems that don't match what candidates actually report seeing.

If system design is part of your loop, practice narrating tradeoffs out loud, ACID versus BASE, horizontal versus vertical scaling, idempotency, rather than just sketching boxes on a whiteboard silently. The candidates who do well in Mastercard's system design round consistently talk through why they rejected an alternative approach, not just what they chose.

Get the reps in before the real thing

Reading through fifty questions is not the same as defending a live answer once an interviewer asks a specific follow-up about your ledger design or pushes on your ethics story for a second, harder example. LastRoundAI's mock interview mode runs live rounds, technical and behavioral, with real-time follow-ups in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19/mo if a handful of sessions isn't enough runway before a real Mastercard loop.

Once your answers hold up under a follow-up, the slower part of the process is usually just getting in front of enough payments and fintech roles that actually run a loop like this one instead of a generic checklist interview. Auto-Apply queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.

Questions about either product go to contact@lastroundai.com.

How this list was built

Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.

What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.

If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.

Frequently asked questions

What behavioural questions come up at Mastercard?

Ownership under pressure, handling a mistake with financial or client consequence, and working with people who disagree with you. Regulated environments care about judgement and escalation, so answers that show you knew when to raise something land well.

How should I prepare for the final round at Mastercard?

Know the business line you are joining, not just the firm. Candidates who can name what the desk or team actually does, and ask a specific question about it, separate themselves quickly at this stage.

What does the Mastercard interview process look like?

Typically a recruiter screen, one or two technical or case rounds, and a final round with senior staff. Technology roles add a coding assessment; front-office roles lean harder on market awareness and behavioural depth. Expect the bar on precision to be higher than at a typical product company.

How technical is the Mastercard interview?

Technical enough that hand-waving fails. For engineering roles expect data structures, system design and questions about correctness under concurrency or scale. For analyst roles expect quantitative reasoning and comfort explaining a number you produced.

Leave a Reply

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