How to Pass the Mastercard SDE-2 LLD Interview

Mastercard's SDE-2 interviews have a reputation for brutally rigorous Low-Level Design (LLD) rounds. As a global payments network processing billions of transactions, their engineering culture prioritizes high throughput, absolute fault tolerance, and strict latency boundaries. They do not just want you to write code that passes a few functional test cases. You are expected to build a production-ready, highly extensible, and completely thread-safe system right there on the spot, usually within a tight 90-minute window.
Designing a real-time fraud rule engine is one of the most common—and intimidating—prompts they throw at candidates. It perfectly tests your grasp of object-oriented programming, concurrency, and design patterns. You have to demonstrate that you can take a vague business requirement, translate it into a scalable class architecture, and write clean, compiling code that handles edge cases gracefully. Let's walk through exactly what you will face in their hiring pipeline and how to architect a bulletproof solution that will easily pass the hiring committee.
The Mastercard SDE-2 Interview Process
| Round | Focus | Duration |
|---|---|---|
| Online Assessment | DSA and basic SQL queries | 90 mins |
| Technical Round 1 | Data Structures & Algorithms (Medium/Hard) | 60 mins |
| Technical Round 2 (LLD) | Low-Level Design & Machine Coding | 90 mins |
| System Design (HLD) | Distributed systems, scalability, databases | 60 mins |
| Hiring Manager | Behavioral, past projects, culture fit | 45 mins |
For the SDE-2 level, the expectations shift significantly compared to entry-level roles. Interviewers are evaluating your system ownership. During the machine coding round, you will typically be given a problem statement and asked to write working code in an IDE or a collaborative editor. You are not expected to build a fully functioning Spring Boot application with an actual database connection, but your core business logic, interfaces, and class structures must be logically complete. Mocking external services and repositories is perfectly acceptable, provided your core design patterns are robust.
Deconstructing the Fraud Rule Engine Prompt
When you reach the LLD round, your interviewer will usually drop a deliberately vague requirement on your lap: "Design a system that evaluates incoming transactions against a set of fraud rules in real-time." The biggest mistake you can make here is immediately opening your editor and writing a Transaction class. Your first job is nailing down the system constraints and failure modes. You need to treat the interviewer like a product manager and clarify the boundaries of the system.
If you start coding without establishing the throughput and latency requirements, you might design a system that relies on heavy synchronous database calls, which will instantly fail you when the interviewer reveals the system needs to process 10,000 transactions per second. You also need to establish the operational requirements of the engine itself, particularly around deployment and rule lifecycle management.

- Latency bounds: What is the maximum acceptable time to evaluate a transaction? (Usually < 50ms).
- Rule complexity: Are rules simple boolean checks, or do they involve nested logic? (Assume complex nested logic).
- Failure handling: If an external service timeout occurs while checking a rule, do we default to blocking or allowing the transaction?
- Dynamic updates: Do new fraud rules need to be added at runtime without restarting the application service?
- Auditability: Do we need to return just a boolean decision, or do we need to return the specific reason codes for why a transaction was flagged?
How do we model the core entities and relationships?
Start by picking out the primary nouns to form your domain model. You will obviously need a Transaction class to hold metadata like the transaction amount, merchant ID, user ID, currency, and timestamp. Make this class immutable. In a highly concurrent environment, passing immutable objects between threads eliminates an entire category of race conditions. Next, you need a Rule interface. Real-world fraud rules get complicated fast. Consider a business requirement like: Block the transaction if Amount > 1000 AND (MerchantCategory == Crypto OR Country != US).
That kind of nested, tree-like logic should immediately point you toward the Composite Design Pattern. Using the Composite pattern lets you treat single rules (leaf nodes) and complex combinations of rules (composite nodes) exactly the same way. The evaluation engine does not need to know if it is evaluating a single amount check or a massive nested tree of logical operators; it just calls the evaluate method on the root node.
- FraudRule (Interface): The base component. Contains a single method: RuleResult evaluate(Transaction t).
- AmountThresholdRule (Leaf): Implements FraudRule. Returns true if the transaction amount exceeds a configured threshold.
- CountryBlockRule (Leaf): Implements FraudRule. Checks the transaction country against a blocklist.
- AndCompositeRule (Node): Implements FraudRule. Contains a List<FraudRule>. Iterates through the list and returns true only if ALL child rules return true. Short-circuits on the first false.
- OrCompositeRule (Node): Implements FraudRule. Contains a List<FraudRule>. Returns true if ANY child rule returns true. Short-circuits on the first true.
Walkthrough: Building a Complex Rule Tree
To prove your design works, write a quick setup method during the interview that constructs the exact rule we discussed earlier: Amount > 1000 AND (MerchantCategory == Crypto OR Country != US). You instantiate the leaf nodes first. Create an AmountThresholdRule configured with 1000. Create a MerchantCategoryRule configured for Crypto. Create a CountryRule configured for US with an exclusion flag.
Next, you wrap the Merchant and Country rules inside an OrCompositeRule. Finally, you take the Amount rule and the newly created OrCompositeRule and wrap them both inside an AndCompositeRule. This AndCompositeRule becomes the root of your evaluation tree. When a transaction comes in, the engine simply calls evaluate on the root. The execution naturally flows down the branches. Because you implemented short-circuiting in your composite nodes, if the amount is only $50, the AndCompositeRule returns false immediately without ever evaluating the expensive Merchant or Country checks, optimizing CPU cycles.
Implementing the Rule Evaluation Engine
The RuleEngine class is the orchestrator. Its primary responsibility is to accept a Transaction, pass it to the root fraud rule, and translate the result into an actionable business decision. A naive implementation would just return a boolean. Do not do this. In a real payments system, the business needs to know exactly why a transaction was blocked for auditing and customer support purposes.
Instead of returning a boolean, your evaluate method should return a custom RuleResult object. This object should contain a boolean isFraudulent flag, a string ruleId indicating which specific rule triggered the block, and a message. When your OrCompositeRule finds a match, it should bubble up the specific RuleResult from the child node that triggered it, rather than creating a generic blocked response. This attention to observability will heavily impress your interviewer.
Handling External Data and Velocity Checks
Eventually, the interviewer will throw a wrench in your design: 'What if a rule needs to check how many times this user has transacted in the last 24 hours?' This is known as a velocity check, and it requires historical data. You cannot store this data in the transaction object, and you absolutely should not put database calls inside your rule classes. Doing so violates the Single Responsibility Principle and makes your rules impossible to unit test effectively.
To solve this, introduce a TransactionContext object. The context acts as a data container that travels alongside the transaction. Before the RuleEngine evaluates the rules, a separate DataHydrationService fetches the necessary user profile and velocity data from a fast caching layer like Redis, populates the TransactionContext, and passes both the transaction and the context to the rule tree. Now, your VelocityRule simply reads the historical count from the context. The rule remains purely logical, stateless, and instantly testable.
How do we ensure thread safety and low latency?
Keep in mind that Mastercard processes thousands of transactions every single second. Your RuleEngine is going to get hammered by multiple threads simultaneously from the web server or message broker. If you want to guarantee thread safety without introducing massive locking bottlenecks, your architecture must rely on statelessness. The Rule implementations must not contain any mutable instance variables. All required state—the transaction data and the context—must be passed as method arguments on the thread's stack.
For latency, if you have multiple expensive independent rules (for example, checking two different third-party blocklist APIs), evaluating them sequentially is a mistake. You should mention the use of Java's CompletableFuture to fan out these checks to a dedicated thread pool. By using CompletableFuture.allOf(), you can execute multiple heavy rules in parallel and bound the maximum wait time using the .orTimeout() method. If a third-party API takes longer than 50ms, the timeout triggers, and you can gracefully degrade the evaluation to a safe default.
How do we handle dynamic rule updates?
Fraud patterns evolve incredibly fast. When a new fraud ring is detected, the operations team needs to deploy new rules instantly. The interviewer will ask how you add new rules without restarting the entire Spring Boot service. The Observer pattern combined with atomic references is the standard enterprise solution here.
Set up a configuration service that polls a database or listens to a Kafka topic for rule changes. Inside your RuleEngine, store the root FraudRule inside a java.util.concurrent.atomic.AtomicReference. When the configuration service detects a change, it builds an entirely new rule tree in the background. Once the new tree is fully constructed, it calls atomicReference.set(newRootRule). Because object reference assignment is atomic in Java, concurrent transactions currently evaluating against the old tree will finish safely, while all new transactions immediately start using the new tree. You achieve zero downtime and zero lock contention.
Common Pitfalls to Avoid in the Interview
- The God Class: Putting all the if-else logic inside a single massive RuleEngine class. This violates the Open-Closed Principle and will result in an immediate rejection.
- Mutable State in Rules: Creating instance variables in your rule classes to track counts or store transaction data. This will cause catastrophic race conditions under concurrent load.
- Ignoring Edge Cases: Failing to handle null fields in the Transaction object. Always validate inputs or use Java Optional where appropriate before executing rule logic.
- Over-engineering early: Do not start writing Kafka consumers or complex database repository interfaces. Focus purely on the core domain models, the rule tree, and the evaluation engine first. Stub the rest.
Mastering the LLD round at Mastercard requires shifting your mindset from a script-writer to a system architect. By organizing your logic with the Composite pattern, separating data fetching from evaluation, and ensuring lock-free thread safety with AtomicReference, you will present a solution that not only passes the interview but mirrors the actual architecture used in top-tier financial systems.
Frequently asked questions
What programming language should I use for Mastercard's LLD round?
Java is definitely the go-to language for backend roles at Mastercard, mostly because they rely heavily on the Spring Boot ecosystem. That said, if you're highly proficient in C++ or Python, interviewers will usually accept those as well.
How much working code is expected in a 90-minute LLD round?
You'll need to write logically complete, compiling code for the core rule engine. Don't worry too much about boilerplate, though. Things like getters, setters, and actual database connections can usually just be mocked or stubbed out.
Do I need to know Spring Boot for the Mastercard SDE-2 interview?
The LLD round itself is mostly about core OOP principles and design patterns. However, knowing Spring Boot is a massive advantage for the System Design (HLD) and hiring manager rounds since Mastercard uses it extensively under the hood.
How do I test my LLD code during the interview?
The easiest way is to write a quick main method, or use JUnit if the interview platform supports it. Just spin up a few dummy transactions and run some assertions to prove your composite rules evaluate everything correctly.
Related comparisons
See AcePrompt in action
Watch how AcePrompt supports a real technical round - structured answers, tuned to your resume, in real time.
Ace your next LLD interview with real-time AI guidance. Try AcePrompt today.
Get started