JPMorgan Chase Software Engineer Interview Questions · 2026

JPMorgan Chase Software Engineer Interview Questions (2026)

JPMorgan Chase runs one of the most process-heavy technology hiring pipelines in financial services. From the initial HireVue video screen to the "super day" coding and design rounds, the full loop can take 8 to 12 weeks and involves more distinct stages than you'd see at most comparable tech companies. The upside: if you know the structure in advance, none of it is a surprise. The downside: there's no shortcut around it.

This page covers what the 2026 JPMorgan Chase software engineer interview actually looks like, based on candidate reports from Glassdoor, Taro, Exponent, and GeeksForGeeks between 2024 and early 2026. Where questions are team-specific or difficulty varies by seniority, I've called that out. The coding bar is not Google-tier, but the process is long, finance-domain awareness genuinely matters in the system design round, and the behavioral component is taken seriously by hiring managers.

8-12 weeksProcess
4-6Rounds
LC Easy/MediumCoding
HireVue + Super DayFormat

Easy questions

20

The expected sum of 1 through n is n*(n+1)/2. Subtract the actual sum of the array. The difference is the missing number. O(n) time, O(1) space. Alternatively, XOR all numbers from 1 to n, then XOR with all elements in the array; the result is the missing number.

Reported as an initial OA question from a December 2024 interview experience. Simple on its face, but interviewers often follow up with: "What if the array could contain duplicates?" or "What if there are two missing numbers?" Those variants require different approaches (sorting, or two-variable equations).

Walk through the price array and sum up every positive day-over-day difference. If price[i+1] > price[i], add the difference. This captures every profitable trade as if you can buy and sell on consecutive days. No DP table needed for the simplest version (unlimited transactions).

If the constraint changes to one transaction only, scan for the minimum price seen so far and track the maximum profit as you go. A common JPMorgan OA question according to multiple 2024-2025 reports. The "multiple transactions with cooldown" variant does require DP, and that follow-up has been reported in live coding rounds at the super day.

python
def max_profit_unlimited(prices):
    profit = 0
    for i in range(1, len(prices)):
        if prices[i] > prices[i - 1]:
            profit += prices[i] - prices[i - 1]
    return profit

def max_profit_one_trade(prices):
    min_price = float('inf')
    max_profit = 0
    for price in prices:
        min_price = min(min_price, price)
        max_profit = max(max_profit, price - min_price)
    return max_profit

Count character frequencies in the first string, then decrement for each character in the second. If any count is nonzero at the end, they are not anagrams. Alternatively, sort both strings and compare directly, O(n log n) but the same result. Both approaches are valid; interviewers at JPMorgan typically ask which you'd choose in production and why.

Reported from coding assessments going back to 2024. It is an easy question, and the real test is whether you handle edge cases: different lengths should return false immediately, null input, Unicode characters outside ASCII. Interviewers who probe this question want to see production-quality thinking, not just algorithm correctness.

Two-pointer technique: a slow pointer advances one node per step, a fast pointer advances two. When the fast pointer reaches the end, the slow pointer is at the midpoint. For even-length lists, this returns the second of the two middle nodes; clarify with your interviewer whether the first or second is expected.

Reported as a phone screen question. The follow-up is often: "Now reverse the second half of the list and check if the whole thing is a palindrome," which combines this technique with the standard linked list reversal problem. That two-part version has shown up in the super day coding round.

Seed a queue with the root. On each pass through the outer loop, capture the queue's current size before processing anything, since that size is exactly the number of nodes at the current depth. Pop that many nodes, record their values, and push their children for the next level.

A common follow-up: "return the average value at each level," or "return only the rightmost node at each level." Both reuse the same level-size snapshot with a small change to what gets recorded per level.

java
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int levelSize = queue.size();
        List<Integer> level = new ArrayList<>();
        for (int i = 0; i < levelSize; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        result.add(level);
    }
    return result;
}

Floyd's tortoise and hare. A slow pointer advances one node per step, a fast pointer advances two. If the list has a cycle, the fast pointer eventually laps the slow one and they land on the same node. If the fast pointer reaches null first, there's no cycle. O(n) time, O(1) space, no hash set required.

The standard follow-up: find where the cycle begins. Once the pointers meet, reset one pointer to the head and advance both one step at a time; where they meet the second time is the start of the cycle. Reported as a two-part phone screen question, detect first, then locate.

python
def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

Push every opening bracket onto a stack. On a closing bracket, pop the stack and confirm it matches the expected opener; if the stack is empty at that point, or the popped bracket doesn't match, the string is invalid. At the end, the stack must be empty, otherwise there are unclosed openers left over.

Reported as a warm-up in a 2025 OA. Worth practicing the variant where the interviewer asks for the index of the first character that breaks validity, rather than a plain true or false, since a couple of candidates said that was the live follow-up.

Comparable defines the natural ordering of a class: the class itself implements compareTo(). Comparator is an external strategy object that defines a custom ordering without modifying the original class. Use Comparable when there's one obvious "natural" order (e.g., integers sorted numerically). Use Comparator when you need multiple orderings (sort by name, then by salary, then by hire date) or when you can't modify the class you're sorting.

The practical answer at JPMorgan: Comparator is used far more in real codebases because you rarely control every class you sort, and requirements change. Comparator.comparing() chained with .thenComparing() is the idiomatic modern approach in Java 8 and later. Interviewers who ask this want to see that you know both and can choose the right one for a context, not just define them.

Dependency injection means a class doesn't construct its own collaborators. The Spring container builds them and hands them in, through the constructor, a setter, or a field annotated with @Autowired. Constructor injection is the style the Spring team recommends by default: it makes required dependencies explicit in the class's signature, supports making fields final, and lets you instantiate the class in a plain JUnit test without spinning up a Spring application context. Field injection is the most common pattern in tutorials and legacy code, but it hides a class's real dependencies and forces reflection-based test setup.

The follow-up JPMorgan interviewers ask most often: "why does field injection make testing harder?" There's no constructor to call with a mock, so you either need Spring's test context or reflection tricks just to set a private field.

java
@Service
public class PaymentService {
    private final AccountRepository accountRepository;
    private final AuditLogger auditLogger;

    // Constructor injection: dependencies are explicit and the fields can be final
    public PaymentService(AccountRepository accountRepository, AuditLogger auditLogger) {
        this.accountRepository = accountRepository;
        this.auditLogger = auditLogger;
    }
}

The LIMIT/OFFSET version works until there's a tie: SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1. The window-function version handles ties correctly, since DENSE_RANK() assigns duplicate values the same rank instead of skipping past them.

Most JPMorgan interviewers push toward the second version, and the follow-up is almost always: "what if two employees share the highest salary, does your first query still return the correct second-highest?" It doesn't. LIMIT/OFFSET treats every row as distinct regardless of value, so a tie at the top silently returns the true highest salary again as the "second highest."

sql
-- Handles ties correctly
SELECT salary
FROM (
    SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
) ranked
WHERE rnk = 2;

JPMorgan is big enough that "I want to work at a top financial institution" is too generic to land. Interviewers expect specificity: the specific team or product area, what attracted you to the technology stack or domain, or a connection between JPMorgan's stated strategic direction and your background. JPMorgan published an AI and machine learning initiative roadmap in 2024 (their IndexGPT patent, their LLM adoption plans, their 2024 AI hiring surge) that gives technically-oriented candidates something concrete to reference.

The worst answers are either purely financial ("the compensation") or purely brand-driven ("it's a prestigious company"). The strongest answers connect your specific background to something JPMorgan is specifically doing. A candidate with payment processing experience who points to JPMorgan's expansion of Zelle and real-time payments infrastructure is going to land better than a generic "excited about fintech" answer.

Reported from a 2025 super day behavioral round. The question is specifically about learning mindset and how you operate when debugging goes sideways. Interviewers don't want to hear that you never get stuck. They want to hear how you diagnose the situation, when you decide to ask for help (and from whom), and how you avoid the same trap in the future.

Useful details to include: the specific tools or techniques you use to change your thinking when stuck (rubber duck debugging, writing down assumptions, time-boxing research), how you decide when to escalate vs. keep working alone, and what artifact you leave behind afterward (a comment, a doc, a test case) so the next person doesn't lose the same time.

Reported often given how closely JPMorgan's engineering teams sit next to trading desks, product owners, and compliance staff who don't read code. The structure that works: state the problem in one plain sentence before any technical detail, follow immediately with the business impact in terms the stakeholder actually cares about (money, risk, a deadline), and only go into implementation detail if asked.

A concrete example beats a general principle every time. "I told the product owner the batch job was failing" is weaker than "I told her the overnight reconciliation report wouldn't be ready before the 7am desk open, and gave her two options with different risk trade-offs so she could decide." The second version shows judgment alongside communication.

Three to four back-to-back 45-minute rounds with short breaks between them is mentally heavier than most candidates expect going in. Treat the PR review component, if your loop includes one, as a skill to drill beforehand rather than something to figure out live: one reported super day used the same code snippet across two consecutive rounds, and being slow to spot the planted issues ate into the time left for the design conversation.

If a virtual super day spans a meal window, eat before it starts. Some candidate accounts describe no scheduled break long enough for a real meal. And if a round runs long for reasons outside your control, say something rather than letting the next round get rushed silently: "I want to make sure we get to the design discussion, should we move on?" is a small sentence that signals the same time-management awareness JPMorgan is trying to screen for in the rounds themselves.

Most candidates report 8 to 12 weeks from initial application to verbal offer. The recruiter phone screen to super day window can be 2 to 4 weeks on its own. Delays are common between the super day and the final decision, often 1 to 2 weeks more. Campus recruiting for the Software Engineer Program has fixed timelines and moves somewhat faster. Referrals can compress the process, sometimes halving the total time.

Not universally, but for any role that explicitly lists Java in the job description (a significant portion of JPMorgan's engineering openings), Java fluency is effectively required. Interviewers for those roles ask Java-specific questions on garbage collection, concurrency, and OOP patterns regardless of what language you code in during the session. Python-first candidates applying to general SWE roles without a Java specification tend to have more flexibility.

Code for Good is JPMorgan's technology hackathon for students and new grads. After a HireVue video screen and coding assessment, selected candidates attend a 24-hour virtual hackathon where they build software prototypes for nonprofit organizations, working alongside JPMorgan engineers. Strong performance in the hackathon often converts directly into Software Engineer Program offers. It is a separate recruiting track from the general full-time hiring process and has its own application cycle.

Noticeably easier, by most candidate reports. The OA problems are typically easy to medium LeetCode difficulty, focused on array manipulation, strings, linked lists, and basic trees. Multiple candidates have described it as "testing fundamentals, not expertise." Google and Meta OAs run harder and have tighter time limits. That said, the live coding rounds at the super day are a step up from the OA, and interviewers probe edge cases and code quality more carefully than the automated OA does.

The system design round is finance-adjacent in most reported super days: fraud detection, payment processing, rate limiting for trading APIs, financial news aggregators. Pure algorithm coding rounds are domain-agnostic. Behavioral rounds probe JPMorgan-specific values (client focus, integrity, accountability) but don't require finance knowledge. You don't need to be a finance expert, but knowing the terms "PCI-DSS," "transaction atomicity," "regulatory audit trail," and "settlement latency" signals enough domain awareness to avoid the most common design round failure mode.

The HireVue is an asynchronous video interview: questions appear on screen and you record a response within a 3-minute window per question, with 30 seconds to think beforehand. Two behavioral questions are typical, covering learning experiences, career goals, and situational scenarios. You usually get two attempts per question. JPMorgan's system explicitly states your application won't advance without completing it, so don't skip it thinking it's optional. Prep by practicing speaking clearly and concisely to a camera; the AI evaluation cares about verbal clarity and confidence alongside the content of your answer.

Medium questions

22

Sort each string's characters to generate a canonical key, then group all strings that share that key using a hash map. The sorted characters are the key; the value is a list of original strings that map to it. Return the hash map's values.

Reported across multiple candidate accounts as both an OA and a live coding question. The follow-up interviewers tend to ask is: "What if the strings are very long? Sorting is O(k log k) per string where k is length. Is there a way to avoid the sort?" The alternative is counting character frequencies instead, which is O(k) per string but requires a map or array of 26 values as the key.

java
import java.util.*;

public class GroupAnagrams {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<>();
        for (String s : strs) {
            char[] chars = s.toCharArray();
            Arrays.sort(chars);
            String key = new String(chars);
            map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
        }
        return new ArrayList<>(map.values());
    }
}

Sort intervals by start time. Iterate through the sorted list; if the current interval's start is less than or equal to the previous merged interval's end, extend the end. Otherwise, add the current interval as a new entry. Return the merged list.

Reported from multiple OA and live coding round accounts. Edge cases to address: a single interval, intervals that are adjacent but not overlapping (most interviewers at JPMorgan want [1,3] and [3,5] merged into [1,5], but confirm), and an input that is already sorted.

Maintain a min-heap capped at size k. Push every element onto the heap; whenever the heap grows past k entries, pop the smallest. Once every element has been processed, the heap's root is the kth largest value. Time is O(n log k), which beats sorting the whole array whenever k is meaningfully smaller than n.

The follow-up interviewers ask most often: "now the array is a live stream of trades coming in continuously, and you need the kth largest at any point in time." That's the same heap, wrapped in a class that exposes an add() method instead of processing a fixed array up front.

python
import heapq

def find_kth_largest(nums, k):
    heap = []
    for n in nums:
        heapq.heappush(heap, n)
        if len(heap) > k:
            heapq.heappop(heap)
    return heap[0]

Scan every cell. Whenever you find an unvisited cell that belongs to a region, increment a counter and run DFS or BFS outward from that cell to mark every connected cell in the same region as visited, so the outer scan skips it later. Time is O(rows * cols).

One candidate reported this exact problem framed as "identify clusters of accounts flagged for related suspicious activity," a finance-flavored wrapper on the standard "number of islands" problem. The algorithm doesn't change, but naming the pattern out loud, this looks like connected components, here's how I'd map the grid to a graph, reads better than silently pattern-matching without saying so.

java
public int countRegions(int[][] grid) {
    int count = 0;
    for (int r = 0; r < grid.length; r++) {
        for (int c = 0; c < grid[0].length; c++) {
            if (grid[r][c] == 1) {
                count++;
                sink(grid, r, c);
            }
        }
    }
    return count;
}

private void sink(int[][] 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;
    sink(grid, r + 1, c);
    sink(grid, r - 1, c);
    sink(grid, r, c + 1);
    sink(grid, r, c - 1);
}

Combine a hash map for O(1) key lookup with a doubly linked list ordered by recency of use. On a get, move the accessed node to the front of the list. On a put, insert a new node at the front, and if the cache is over capacity, evict the node at the back, the least recently used one, along with its entry in the hash map.

Multiple 2024 and 2025 super day accounts named this one of the most repeated "implement this from scratch" questions at JPMorgan, likely because caching pricing data, session state, and rate-limit counters shows up constantly in trading infrastructure. Interviewers usually want the actual doubly linked list version. Saying "I'd use LinkedHashMap" and stopping there is a thinner answer, though mentioning that Java's LinkedHashMap supports this natively through removeEldestEntry is worth a sentence to show you know the shortcut exists.

python
class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = {}
        self.order = []

    def get(self, key):
        if key not in self.cache:
            return -1
        self.order.remove(key)
        self.order.append(key)
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.order.remove(key)
        elif len(self.cache) >= self.capacity:
            oldest = self.order.pop(0)
            del self.cache[oldest]
        self.cache[key] = value
        self.order.append(key)

The list.remove() and list.pop(0) calls above are O(n), fine for talking through the logic on a whiteboard but worth flagging as a limitation. A production version uses an actual doubly linked list with node references so every operation runs in O(1).

The most commonly reported issues in JPMorgan PR review exercises: hardcoded API keys or passwords as string literals (a security issue), method names that don't describe what the method does, caught exceptions that are silently swallowed instead of logged or rethrown, missing input validation before acting on user-supplied data, and mutable objects returned from getter methods that expose internal state.

One reported super day involved 10 minutes reviewing a PR before moving to a coding problem, and a separate super day used the same PR snippet in both the coding and system design rounds, which ate into the candidate's time for the design question. Budget for this. If you spot issues quickly, that's the best outcome. If you're slow on it, you may find yourself rushing the design conversation.

java
// Example of the kinds of issues planted in PR review exercises

public class PaymentService {
    // Issue 1: hardcoded credential
    private static final String API_KEY = "sk_live_abc123xyz";

    public void processPayment(String userId, double amount) {
        // Issue 2: no null check on userId
        // Issue 3: silently ignoring the exception
        try {
            sendToProcessor(userId, amount);
        } catch (Exception e) {
            // swallowed - no log, no rethrow
        }
    }

    // Issue 4: mutable list returned directly - caller can mutate internal state
    public List<String> getPendingTransactions() {
        return pendingList; // should return Collections.unmodifiableList(pendingList)
    }
}

A HashMap stores entries in an array of buckets. The bucket index is computed from the key's hash code modulo the array length. When two keys hash to the same bucket (a collision), Java handles this with a linked list at that bucket (chaining). In Java 8 and later, when a bucket's linked list grows beyond 8 entries, it converts to a balanced red-black tree, reducing worst-case lookup from O(n) to O(log n).

Reported from both phone screens and super day rounds. Follow-ups: "What's the default load factor?" (0.75, meaning the map resizes when 75% of buckets are occupied). "What happens during a resize?" (a new array is allocated, all entries are rehashed into new positions). "What can go wrong if you use a mutable object as a key?" (if the object's hashCode changes after insertion, you can no longer find the entry).

java
// Illustrating HashMap internal structure

// Java 7 and earlier: each bucket is a linked list
// Java 8+: buckets with >8 entries become TreeNodes (red-black tree)

// The key contract: if equals() returns true, hashCode() must return the same value
// Violating this breaks HashMap lookups

public class BadKeyExample {
    private int value;

    // PROBLEM: mutable field affects hashCode
    @Override
    public int hashCode() {
        return value; // changes if value is modified after insertion
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof BadKeyExample)) return false;
        return this.value == ((BadKeyExample) obj).value;
    }
}

Single Responsibility: a class should have one reason to change. Open/Closed: open for extension, closed for modification (use abstract classes or interfaces, not if-else chains that grow with every new type). Liskov Substitution: a subclass should be substitutable for its parent without breaking the program. Interface Segregation: prefer small, specific interfaces over one large general one. Dependency Inversion: depend on abstractions, not concrete implementations.

At JPMorgan, the SOLID questions are usually framed around a real scenario: "We have a PaymentProcessor class that handles credit cards, wire transfers, and crypto. How would you refactor it to follow SOLID?" The expected answer introduces an interface (or abstract class) for each payment method, with a factory or strategy pattern to select the right implementation at runtime.

java
// Before: violates Open/Closed and Single Responsibility
public class PaymentProcessor {
    public void process(String type, double amount) {
        if (type.equals("credit")) { /* credit card logic */ }
        else if (type.equals("wire")) { /* wire transfer logic */ }
        // Every new type adds another else-if
    }
}

// After: Open/Closed via interface + strategy pattern
public interface PaymentStrategy {
    void process(double amount);
}

public class CreditCardPayment implements PaymentStrategy {
    public void process(double amount) { /* credit card logic */ }
}

public class WireTransferPayment implements PaymentStrategy {
    public void process(double amount) { /* wire transfer logic */ }
}

public class PaymentProcessor {
    private final PaymentStrategy strategy;

    public PaymentProcessor(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void process(double amount) {
        strategy.process(amount);
    }
}

A singleton ensures only one instance of a class exists in the JVM. Thread-safe implementations: the double-checked locking pattern with a volatile field prevents two threads from both passing the null check and each creating an instance. The enum singleton is simpler and serialization-safe (the JVM guarantees exactly one instance of each enum value). The initialization-on-demand holder idiom uses the JVM's class loading guarantee for lazy initialization without synchronization overhead.

Reported from multiple JPMorgan interview accounts as both a standalone question and as a follow-up to design pattern questions. The correct follow-up answer when asked "what are the downsides of singleton?" is that it creates hidden global state, makes unit testing harder (you can't easily swap the instance with a mock), and introduces tight coupling.

java
// Thread-safe singleton: initialization-on-demand holder idiom
public class DatabaseConnection {
    private DatabaseConnection() {}

    private static class Holder {
        // Loaded by the classloader only when Holder is first accessed
        private static final DatabaseConnection INSTANCE = new DatabaseConnection();
    }

    public static DatabaseConnection getInstance() {
        return Holder.INSTANCE;
    }
}

// Even simpler: enum singleton (JVM guarantees single instance + serialization safe)
public enum ConfigService {
    INSTANCE;

    public String getConfig(String key) {
        // implementation
        return System.getenv(key);
    }
}

Java's garbage collector automatically reclaims memory by collecting objects with no live references. The JVM divides heap into generations: young generation (Eden + survivor spaces, short-lived objects), old generation (long-lived objects), and metaspace (class metadata). Minor GC runs frequently and is fast; major/full GC is slower and causes stop-the-world pauses.

Common GC algorithms: G1 (Garbage First, the default since Java 9) targets predictable pause times by dividing the heap into regions and prioritizing the most garbage-dense ones. ZGC and Shenandoah (available from Java 15+) offer ultra-low pause times (sub-millisecond) by doing most work concurrently. For latency-sensitive financial systems, ZGC is increasingly the standard answer because pause time variability in G1 can cause tail latency spikes during heavy GC cycles.

Collections.synchronizedMap(new HashMap()) wraps every method call in a single lock, so all reads and writes are serialized. ConcurrentHashMap uses lock striping: in Java 8 and later, it uses CAS (compare-and-swap) operations for most writes, with synchronized blocks only on individual buckets when a true lock is needed. This allows multiple threads to read and write to different buckets concurrently without contention.

For read-heavy workloads, ConcurrentHashMap's throughput advantage is significant. For write-heavy workloads with high contention on the same keys, the gap narrows. The other difference: ConcurrentHashMap's iterators reflect the state of the map at the time of iteration without throwing ConcurrentModificationException, though they may not reflect concurrent insertions during the iteration.

By default, Spring's transaction manager rolls back only on unchecked exceptions, meaning RuntimeException, its subclasses, and Error. A checked exception thrown from inside a @Transactional method does not trigger a rollback unless you explicitly configure @Transactional(rollbackFor = SomeCheckedException.class). This surprises people who assume any exception rolls back the transaction.

Reported as a follow-up specifically when a candidate mentions building a payments or ledger service in Spring, since a silently committed transaction after a business-rule failure is exactly the bug a financial system can't afford. If a custom "insufficient funds" exception is checked and rollbackFor was never set, the debit can commit even though the code threw an exception to stop it.

java
@Transactional(rollbackFor = InsufficientFundsException.class)
public void transfer(String fromAccount, String toAccount, BigDecimal amount)
        throws InsufficientFundsException {
    Account source = accountRepository.findById(fromAccount);
    if (source.getBalance().compareTo(amount) < 0) {
        throw new InsufficientFundsException(fromAccount);
    }
    accountRepository.debit(fromAccount, amount);
    accountRepository.credit(toAccount, amount);
}

Require the client to send an idempotency key, a UUID generated once per logical request, in a request header. Before processing, check whether that key has already been seen (a dedicated table, or a Redis entry with a TTL, both work). If it has, return the stored response instead of processing the payment again. If it hasn't, process the request and store the key alongside the response before returning.

This tends to come up as a bridge between the coding round and the payment system design round, since network retries are a certainty in any distributed system, and reprocessing the same transfer twice is the kind of bug that shows up as a customer complaint, not a failed test. Interviewers want to hear "idempotency key" by name. "We'll add a unique ID somewhere" without naming the pattern is a weaker answer.

At startup, Spring Boot inspects the classpath and, based on which dependencies are present, conditionally registers beans through @Conditional annotations such as @ConditionalOnClass and @ConditionalOnMissingBean. If spring-boot-starter-data-jpa is on the classpath and no DataSource bean already exists, Boot wires one up automatically from application.properties, along with an EntityManagerFactory and a transaction manager, without you writing that configuration by hand.

To turn off a specific piece of that behavior, exclude the auto-configuration class directly with @SpringBootApplication(exclude = DataSourceAutoConfiguration.class), or set spring.autoconfigure.exclude in application.properties. Interviewers ask this mostly to check whether you understand what's happening behind the convention rather than simply trusting that it works.

Partition by account_id, order by amount descending, and number the rows within each partition with ROW_NUMBER(), then filter the outer query to rows numbered 3 or lower. Use DENSE_RANK() instead if ties should share a rank rather than each getting a distinct number.

This is the "top N per group" pattern, and it comes up constantly in financial reporting, since reconciliation and risk queries are almost always phrased as give me the top N per account, per trader, or per desk. Reported as a query-writing question for roles touching reporting pipelines rather than pure backend services.

sql
SELECT account_id, transaction_id, amount
FROM (
    SELECT account_id, transaction_id, amount,
           ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY amount DESC) AS rn
    FROM transactions
) ranked
WHERE rn <= 3;

Atomicity means the debit and credit sides of a transfer either both commit or neither does; there's no state where money left one account but never arrived at the other. Consistency means the ledger's invariants, total balance reconciling across every account, hold before and after the transaction. Isolation means two transfers running concurrently don't see each other's uncommitted writes. Durability means a committed transfer survives a crash immediately after commit.

For the isolation level: READ COMMITTED is usually too weak for a balance transfer, since two concurrent transfers touching the same account can both read the same starting balance and both proceed as if the other doesn't exist. SERIALIZABLE is correct but expensive under contention, since it can effectively lock the whole table for competing transactions. The answer that tends to land well is REPEATABLE READ combined with an explicit row lock on the account being debited (SELECT ... FOR UPDATE), which prevents the double-read race without paying for full table serialization.

Reported as a question that shows up inside the payment system design discussion rather than a standalone SQL section, since it connects the database-level guarantee to the distributed-system guarantees, idempotency, sagas, discussed earlier in that conversation.

Directly reported as a Glassdoor question. The STAR structure works here, but the trap is making the story purely about what happened to you rather than what you did. Interviewers want to see that you took an action: you had a direct conversation, you escalated appropriately, you restructured the work so the dependency didn't block your output.

The best answers end with a result that includes what you learned or what you'd do differently. "The situation resolved when our manager intervened" is a weaker answer than "I had a direct conversation with them first, and when that didn't change things, I looped in our tech lead with specifics, which led to a clearer task assignment." The second version shows judgment about escalation and concrete action.

Reported from a Glassdoor entry as: "You are working on a legacy app and running into difficulty, how do you proceed to solve your issues?" The question tests pragmatism over purism. The candidates who struggled answered with "I would rewrite it" without addressing the reality that rewrites at JPMorgan-scale take years and carry significant risk.

The approach interviewers respond to: identify the layer causing the problem before touching anything, build a test harness around the problematic component so you can verify behavior before and after changes, and make targeted changes with clear rollback paths. Mentioning that you documented what you learned for the next engineer who touches it is a small detail that signals operational maturity.

Directly reported as a top behavioral question. The structure that works: be specific about what the competing priorities were and why they conflicted, explain how you assessed urgency vs. importance (not all "urgent" things are equally important), describe what you did to communicate to stakeholders about timing, and give the actual outcome.

At a company like JPMorgan where deadlines sometimes involve regulatory filings or market-hours dependencies, the stakes can be genuinely high. If you have a story from financial services, it tends to land better than a story from a completely unrelated domain. If you don't, frame your story around the consequence of missing the deadline, not just the fact of being busy.

JPMorgan interviews for "intellectual courage" in its engineering leadership principles, which is the organizational language for being willing to raise technical concerns. The failure mode on this question is being either too passive ("I raised it but then just went along") or too unilateral ("I just did it my way"). Neither signal is what hiring managers want.

The story structure that works: you identified a specific technical risk with evidence, you brought it to the right person with a proposed alternative (not just a critique), and you were willing to either change your mind if presented with better information or escalate further if the concern was serious enough. The outcome matters less than the process you followed.

This tests urgency and judgment under pressure at the same time, which maps directly to the "operational excellence" competency JPMorgan screens for. Strong answers follow a clear order: assess the blast radius first (how many customers, how much money, is it still happening), contain the issue (roll back, disable a feature flag, or take the affected path offline) before digging into root cause, communicate a factual status up the chain, and only then debug why it happened.

The weak version of this answer jumps straight into root-cause debugging without addressing containment. Interviewers specifically probe "what did you tell your manager, and when," since silence or a delayed escalation reads as a real problem in an environment where some incidents carry reporting obligations.

JPMorgan wants engineers who build the requirement into the plan rather than treating it as an obstacle to work around. The structure that lands: acknowledge the friction honestly (nobody enjoys the extra process), explain how the requirement got folded into the timeline instead of bolted on at the end, and give a real outcome, including any trade-off you made along the way.

If you don't have a finance-specific example, a story about a security review, a legal sign-off, or an accessibility requirement at a previous job works fine, since the underlying skill, working inside a constraint you didn't choose, is the same. Saying you don't have a story like this at all is a better answer than inventing one on the spot. Interviewers can usually tell.

Hard questions

4

First question: is the queue actually unbounded? Executors.newFixedThreadPool and newCachedThreadPool both default to queue implementations that will happily accept work forever, so under sustained load the queue itself becomes the memory leak even before any thread does anything wrong. Check the ThreadPoolExecutor construction, if it's backed by a LinkedBlockingQueue with no capacity, that's very likely your root cause and not a symptom.

Next, take a thread dump (jstack or a heap dump if it's already OOMing) and look at what the worker threads are actually doing, not just how many there are. If all of them are BLOCKED or WAITING on a socket read or a DB connection acquire, that tells you the pool isn't the problem, a downstream dependency is slow and every thread is stuck waiting on it while new work keeps arriving faster than it drains. If threads are RUNNABLE and burning CPU, you have a different problem, likely a hot loop or GC thrashing.

The fix has two parts. Bound the queue and pick a rejection policy that fails fast (reject with a 503 or use CallerRunsPolicy to apply backpressure) instead of accepting unlimited work. Then put a timeout on whatever the downstream call is, a connection pool with no timeout combined with an unbounded queue is exactly how you get a slow database taking down an otherwise healthy service. Track submitted-vs-completed-vs-active counts as a metric so you catch queue growth in a dashboard, not in a page at 2am.

No, and this trips people up constantly. Kafka's exactly-once guarantee, the idempotent producer plus the transactional API, is scoped to Kafka itself, producing to a topic and reading within a Kafka Streams transaction. The moment you consume a record and write the result to an external system that isn't part of Kafka's transaction coordinator, a plain relational database for instance, you're back to at-least-once at the boundary. A rebalance, a consumer crash after processing but before committing the offset, or a retry after a transient DB error, any of these can redeliver the same message.

So the write to the external system has to be made idempotent independent of what Kafka promises. The usual pattern is to carry the Kafka partition and offset, or a business-level idempotency key from the event payload, and store it in the same database transaction as the business write, either as a unique constraint you upsert against or a small dedup table you check first. If the write and the dedup marker aren't in the same transaction, you can still end up double-applying on a crash between the two steps.

This is also why "exactly once" is a slightly misleading phrase in practice, what you're actually building is at-least-once delivery with effectively-once application, and the effectively-once part is entirely on you to implement at the sink.

My first suspicion is the N+1 problem, you load a list of parent entities, then for each one you touch a lazy-loaded association in a loop, and Hibernate fires one extra query per parent instead of one query for the whole batch. It's invisible in dev with ten test rows and catastrophic in production with ten thousand, which is exactly why it survives code review. Turn on SQL logging, or better, use an APM tool that counts queries per request, and look for a query count that scales linearly with result size.

The straightforward fix is a JOIN FETCH in your JPQL or an @EntityGraph on the repository method, pulling the association in the same query. Where that doesn't fit, hibernate.default_batch_fetch_size turns the N individual lookups into a handful of IN-clause queries instead, which is usually good enough and less invasive than rewriting queries everywhere.

There's a gotcha people hit right after fixing this, if you fetch-join a collection association together with pagination, Hibernate can't paginate at the database level anymore because the join multiplies rows, so it pulls the entire result set into memory and paginates in Java. That's a worse regression than the N+1 you started with. The fix is a two-step query, page the parent IDs first with a simple query, then fetch the associated children for just those IDs in a second query.

These two systems have opposite tolerance for staleness versus unavailability, so they land on opposite sides of CAP even inside the same platform. The system of record for an accepted trade order has to favor consistency. If a network partition happens, you'd rather reject new writes on the minority side than risk two nodes both believing they're authoritative and diverging on whether an order was accepted, because an order that silently gets lost or double-booked is a much worse outcome than a brief unavailability. That means synchronous replication or quorum writes, and accepting the latency cost that comes with it.

A balance display is read-heavy, tolerant of a few seconds of staleness, and users expect it to just work even during a partial outage, so it makes sense to favor availability there, serve from a replica or cache that repairs asynchronously once the partition heals. The caveat is that you can't let a stale AP read masquerade as authoritative everywhere, a regulator-facing statement of account or anything feeding a payment decision needs to fall back to the CP source of record, or at minimum be clearly labeled as as-of a point in time rather than live.

The thing to make explicit in an answer like this is that the underlying ledger itself never stops being CP, what you're actually doing is putting an AP cache in front of a CP system for the read path, not making the whole system AP.

Real-time scenario questions

6

This exact scenario was reported from a 2025 super day. The system ingests articles from multiple financial news sources (Reuters, Bloomberg feeds, SEC filings, earnings transcripts) and surfaces relevant items to traders and analysts based on the securities they follow.

Ingestion layer: multiple crawlers or API feed subscribers, deduplicated by URL hash and content fingerprint (to catch the same article re-posted at different URLs). Processing: NLP entity extraction to tag mentioned tickers and companies, sentiment scoring, and urgency classification (breaking news vs. analysis). Storage: Elasticsearch for full-text search, a relational DB for user subscription mappings and read status.

Latency requirement matters here. A trader following a stock wants to see breaking news within seconds of publication, not minutes. That argues for a push architecture (WebSocket connections to active users) rather than polling. The follow-up reported: "How do you handle 10,000 traders all following Apple, and Apple releases earnings?" That's a fanout problem: you need a publish-subscribe system that fans out the notification without creating a thundering herd on the database.

JPMorgan's trading APIs are rate-limited per client to prevent abuse and to comply with exchange regulations around algorithmic trading. The standard approaches: token bucket (allows short bursts up to a bucket capacity), leaky bucket (smooths traffic to a constant rate), or fixed window counter (simple but allows double the rate at window boundaries).

For a distributed API fleet, per-instance counters don't work: a client can hit different instances and exceed the limit without any single instance knowing. The counter must be centralized in Redis with an atomic increment-and-check operation (using Lua scripts or the SET NX command for atomicity). The alternative is a sliding window log stored per client in Redis, which is more accurate but uses more memory.

For financial systems specifically, you also need a bypass mechanism for certain regulatory or high-priority client categories, and an audit log of every limit hit for compliance review.

Start with the data flow: transactions come in via a stream (Kafka is reasonable to name here), each event contains merchant, amount, location, and cardholder ID. A stream processing layer (Flink or Spark Streaming) applies rule-based checks in real time: velocity checks (too many transactions in a short window from the same card), geo-anomaly checks (transaction in New York 5 minutes after a transaction in London), and merchant category risk scoring.

For latency, rule-based checks must complete in under 100ms to not block the authorization path. ML-based scoring (gradient boosted models or neural nets) can run asynchronously on a shadow path and feed back into the rule engine for future decisions. The model needs to be retrained on new fraud patterns without downtime, so blue-green model deployment with a holdout validation set is the standard pattern.

Data storage: a hot-path cache (Redis) for velocity counters, a data lake (Snowflake or BigQuery) for offline model training and regulatory audit trails. Regulators require that every decision be explainable and logged, which rules out pure black-box approaches without an explanation layer.

The core challenge is atomicity across multiple services and currencies. The transaction must be atomic: if the debit succeeds but the credit fails, you need a compensating transaction to reverse the debit, not just a retry. The saga pattern (choreography or orchestration) handles this for distributed systems; at JPMorgan's scale, they use a combination of message queues with guaranteed delivery and two-phase commit for the critical leg.

Currency conversion rates must be cached with a short TTL (market rates move in seconds for some pairs) and sourced from a reliable feed. For compliance, every transaction must carry the FX rate applied and the timestamp of that rate. Settlement latency differs by currency: USD-to-USD can settle same-day, cross-border SWIFT transfers can take T+1 or T+2.

Interviewers at JPMorgan want to hear about idempotency keys on payment requests, because network retries are guaranteed in a distributed system, and processing the same payment twice is a serious error. They also care about PCI-DSS compliance at the data layer: cardholder data encrypted at rest and in transit, no logging of full card numbers.

Start with what "match" means. You have a source system (say the core ledger) and a downstream system (a reporting mart, a payment rail, a partner bank's feed), and you need to prove every transaction that hit one also hit the other, with the same amount. Naively comparing every field on every row doesn't scale to millions of rows a day, so normalize each side into a canonical record, hash it, and compare hashes. A mismatch means something changed, a missing hash on one side means something didn't propagate.

The part people get wrong is timing. The two feeds rarely arrive in the same window, so you need a tolerance window, unmatched-after-N-minutes goes into an exceptions queue rather than firing an alert immediately, otherwise you get paged for things that resolve themselves five minutes later. Store the unmatched records with a TTL and an escalation path, if something is still unmatched after a business-day boundary that's a real break and needs a human.

The genuinely hard case is correlated failure, both systems miss the same event because they share an upstream dependency. Hash comparison between the two systems won't catch that since they agree with each other and both are wrong. For anything money-moving you want a third, independent source of truth, a clearing house statement or exchange drop copy, that you reconcile against periodically even if it's slower, specifically to catch the case where your two primary systems failed together.

The core trick is to make each instrument single-threaded. Locking a shared order book across threads to guarantee ordering kills your latency and is genuinely hard to get right under contention, so instead you shard by symbol and route every order for a given instrument through a single ingest queue that one thread owns exclusively. That thread is the only writer to that instrument's book, which gives you deterministic ordering for free and removes the need for locks on the hot path.

The book itself is two ordered structures, bids sorted by price descending then time ascending, asks sorted by price ascending then time ascending, typically a price-level map where each level holds a FIFO queue of orders at that price. An incoming order walks the opposite side from the best price, matches whatever quantity it can at each level in time order, and either fully fills, partially fills and requeues the remainder, or rests on the book if nothing crosses.

For durability, every accepted order and every resulting trade gets appended to a write-ahead log before you acknowledge it back to the client, so a crash mid-session can be replayed deterministically from the log to reconstruct the exact book state. You snapshot the book periodically so replay time is bounded rather than growing unboundedly with uptime. On the performance side, GC pauses are the enemy of consistent latency, so a lot of production matching engines minimize allocation on the hot path or run off-heap, and every trade carries a monotonic sequence number so downstream consumers can detect gaps.

What we've seen across JPMorgan loops

Across prep sessions on LastRoundAI for JPMorgan Chase roles, one pattern shows up consistently: candidates who do well technically but freeze when the system design question turns financial. "Design a news aggregator" becomes "design a financial news aggregator with real-time ticker tagging," and the candidate who aced LeetCode mediums has nothing to say about fanout, audit logs, or latency requirements on the authorization path.

JPMorgan doesn't expect you to be a finance expert. But they do expect you to ask clarifying questions that show you understand the stakes are different in a regulated financial environment. "Does this system need to be auditable?" is a question that signals awareness. "I'd just use a standard microservices architecture" without any mention of PCI-DSS or transaction atomicity is the answer that ends rounds early.

The second pattern: the PR review component catches candidates who haven't seen it before. One super day reportedly used the same code snippet in two consecutive rounds, eating 10 to 20 minutes of design time. If you know to expect a PR review and can identify issues quickly, you reclaim that time. Prep a mental checklist: hardcoded credentials, swallowed exceptions, missing null checks, mutable returns, thread safety.

Practice, don't just read
Rehearse a real JPMorgan interview, live

LastRoundAI runs a realistic mock JPMorgan interview and gives you real-time guidance on the exact questions above.

Leave a Reply

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