Back to Self Introduction
Q4Question 4 of 8

Tell me about a time you improved the performance of a slow system: what was slow, what you changed, and the improvement

Key Concepts
  • Always measure before changing anything : performance intuition is unreliable.
  • Quantify the slowness with a real metric such as p99 latency or SLA breach percentage.
  • Profile to find the real bottleneck rather than scattering micro-optimizations.
  • Fix the specific thing the profiler points to, then verify with before-and-after numbers.
  • Check that the fix did not break correctness : a faster wrong answer is worse than a slow right one.
  • State the improvement as a business-relevant number such as latency, throughput, or cost.
  • Mention any trade-off you accepted, such as slightly stale cached data, to keep the answer honest.

Definition

Measure first, fix the real bottleneck, verify with before-and-after numbers : never optimize on a guess.
  • Measure-first discipline – Always profile and quantify the slowness before changing anything, because intuition about performance is famously unreliable.
  • Targeted fix – Change only the specific code path the profiler identifies as the bottleneck, not a scattershot of micro-optimizations.
  • Before-and-after proof – Verify the improvement with the same metric you used to diagnose, producing a concrete number.
  • Correctness guard – Confirm that the faster version still produces correct results, especially with money.
  • Honest trade-offs – If the fix introduces a trade-off such as caching with stale data, state it.

Imagine customers complaining that the loan-approval desk at National Bank of India is slow. A guesser hires more staff randomly. A professional first measures where the time goes : forms take two minutes, but the credit check takes twenty : then fixes the credit check specifically. The improvement is measured: approvals go from twenty-five minutes to seven. That measure-first, fix-the-bottleneck discipline is exactly what this question rewards.

Understanding the Concepts

What is this question really testing?

The interviewer wants to see the discipline of measurement, not just the cleverness of the fix. Starting with "I measured the p99 latency at three seconds, breaching our one-second SLA" immediately signals seniority. Starting with "I thought it was slow so I added caching" signals guessing.

JAVA
package com.bank.service;

import java.util.logging.Logger;

public class MeasureFirst {
    private static final Logger log = Logger.getLogger(MeasureFirst.class.getName());

    public static void main(String[] args) {
        long p99Before = 3000; // ms
        long sla = 1000;       // ms
        log.info(() -> "p99=" + p99Before + "ms, SLA=" + sla + "ms -> BREACH");
    }
}
TEXT
p99=3000ms, SLA=1000ms -> BREACH

How do you find the real bottleneck?

In a banking backend at National Bank of India, the usual suspects are database queries (N+1 patterns, missing indexes), unnecessary serialization, lock contention, or synchronous calls that should be parallel. You profile and look at where the time actually goes, not where you assume it goes.

JAVA
package com.bank.service;

import java.util.List;
import java.util.logging.Logger;

public class BottleneckProfile {
    private static final Logger log = Logger.getLogger(BottleneckProfile.class.getName());

    public static void main(String[] args) {
        // Simulated profiling result
        List<String> breakdown = List.of(
            "Database queries:  2700ms (90%)",
            "Serialization:      200ms (7%)",
            "Business logic:      100ms (3%)"
        );
        breakdown.forEach(b -> log.info(b));
        log.info("Bottleneck: database queries");
    }
}
TEXT
Database queries:  2700ms (90%)
Serialization:      200ms (7%)
Business logic:      100ms (3%)
Bottleneck: database queries

What is the classic N+1 query problem?

The N+1 pattern is the most common performance problem in banking backends. Loading a list and then querying inside a loop fires one query per item. For five hundred accounts, that is over five hundred round-trips.

JAVA
package com.bank.service;

import java.util.List;

public class NPlus1Problem {
    // WRONG: N+1 queries
    public void loadStatementSlow(List<String> accountIds) {
        for (String id : accountIds) {
            // Each call is a separate DB round-trip
            Object txns = findTransactionsByAccount(id);
        }
    }

    // CORRECT: single batched query
    public void loadStatementFast(List<String> accountIds) {
        Object allTxns = findTransactionsByAccountIds(accountIds);
    }

    private Object findTransactionsByAccount(String id) { return null; }
    private Object findTransactionsByAccountIds(List<String> ids) { return null; }
}

How do you fix a missing index?

If the profile shows ninety percent of time in one query, adding an index turns a full table scan into an index seek. For Priya's account statement query, indexing the account_id column makes the lookup near-instant.

SQL
-- Before: full table scan on 10M rows
SELECT * FROM transactions WHERE account_id = 'ACC-2001';

-- Fix: add index
CREATE INDEX idx_txn_account ON transactions(account_id);

-- After: index seek, sub-millisecond
SELECT * FROM transactions WHERE account_id = 'ACC-2001';
JAVA
package com.bank.service;

import java.util.logging.Logger;

public class IndexImprovement {
    private static final Logger log = Logger.getLogger(IndexImprovement.class.getName());

    public static void main(String[] args) {
        long beforeMs = 2700;
        long afterMs = 3;
        log.info(() -> "Query time: " + beforeMs + "ms -> " + afterMs + "ms (index added)");
    }
}

When is caching appropriate?

Caching is appropriate when the data tolerates slight staleness. A balance lookup for a dashboard can use a short-TTL cache, but a balance check before a debit must always read the latest value.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.concurrent.ConcurrentHashMap;

public class BalanceCache {
    private final ConcurrentHashMap<String, BigDecimal> cache = new ConcurrentHashMap<>();
    private static final long TTL_MS = 5000; // 5 seconds

    // Dashboard: cache OK (staleness tolerated)
    public BigDecimal getBalanceForDisplay(String accountId) {
        return cache.getOrDefault(accountId, fetchFromDb(accountId));
    }

    // Transfer: always fresh (correctness required)
    public BigDecimal getBalanceForDebit(String accountId) {
        return fetchFromDb(accountId);
    }

    private BigDecimal fetchFromDb(String accountId) {
        return new BigDecimal("50000.00"); // simulated
    }
}

How do you verify without breaking correctness?

A faster wrong answer is worse than a slow right one, especially with money. Run the optimized path against the same inputs and confirm identical outputs before deploying.

JAVA
package com.bank.service;

import java.util.logging.Logger;

public class CorrectnessCheck {
    private static final Logger log = Logger.getLogger(CorrectnessCheck.class.getName());

    public static void main(String[] args) {
        String slowResult = "[TXN-1, TXN-2, TXN-3]";
        String fastResult = "[TXN-1, TXN-2, TXN-3]";
        boolean match = slowResult.equals(fastResult);
        log.info(() -> "Correctness verified: " + match);
    }
}
TEXT
Correctness verified: true

How do you present the before-and-after result?

State the improvement as a business-relevant number. "p99 dropped from 3000ms to 180ms" is proof. "It got faster" is not.

JAVA
package com.bank.service;

import java.util.logging.Logger;

public class BeforeAfter {
    private static final Logger log = Logger.getLogger(BeforeAfter.class.getName());

    public static void main(String[] args) {
        long p99Before = 3000;
        long p99After = 180;
        double improvement = (1.0 - (double) p99After / p99Before) * 100;
        log.info(() -> String.format("p99: %dms -> %dms (%.0f%% improvement)", p99Before, p99After, improvement));
    }
}
TEXT
p99: 3000ms -> 180ms (94% improvement)

What should a senior engineer emphasize about trade-offs?

Always mention the trade-off honestly. If you added caching, mention the staleness window. If you batched queries, mention the memory trade-off of loading more data at once. Honesty about costs is what separates a senior answer from a junior one.

JAVA
package com.bank.service;

public class TradeOffDisclosure {
    public static void main(String[] args) {
        System.out.println("Fix: added 5s cache for balance display");
        System.out.println("Trade-off: dashboard may show balance up to 5s stale");
        System.out.println("Acceptable: display only, not used for debits");
    }
}

Visual Overview

DIAGRAM
┌────────────────────────────────────────────────────────┐
│         Performance Improvement Workflow                │
├────────────────────────────────────────────────────────┤
│                                                        │
│  ┌──────────────────┐                                  │
│  │  1. MEASURE       │  p99 = 3000ms (SLA = 1000ms)    │
│  └────────┬─────────┘                                  │
│           │                                            │
│           ▼                                            │
│  ┌──────────────────┐                                  │
│  │  2. PROFILE       │  DB queries = 90% of time       │
│  └────────┬─────────┘                                  │
│           │                                            │
│           ▼                                            │
│  ┌──────────────────┐                                  │
│  │  3. FIX           │  Batch N+1 → single query       │
│  │  (targeted)       │  + add missing index             │
│  └────────┬─────────┘                                  │
│           │                                            │
│           ▼                                            │
│  ┌──────────────────┐                                  │
│  │  4. VERIFY        │  Correctness: outputs match     │
│  │                   │  Performance: p99 = 180ms       │
│  └────────┬─────────┘                                  │
│           │                                            │
│           ▼                                            │
│  ┌──────────────────┐                                  │
│  │  5. REPORT        │  94% improvement, SLA met       │
│  │  (before/after)   │  Trade-off: none in this case   │
│  └──────────────────┘                                  │
└────────────────────────────────────────────────────────┘

Key Takeaway

The slow system I improved at National Bank of India was the account-statement screen that customers like Rajesh and Priya use daily. Customers were complaining that pulling up a statement took around three seconds, which breached our one-second latency target, and it was worst for customers with long transaction histories. I owned the fix. The first thing I did was measure rather than guess. I profiled the endpoint and the data was unambiguous: almost all the time was spent in the database, not in the application logic.

When I looked closer, it was a classic N+1 query problem. To build the statement, the code loaded the account and then fired a separate database query for every single transaction. For a customer with five hundred transactions, that was over five hundred round-trips to the database, and each round-trip has a cost that adds up quickly. The fix was targeted at exactly that bottleneck: instead of querying per transaction, I batched them into a single query that fetched all the transactions at once.

The improvement was dramatic and, importantly, measured. The p99 for that endpoint dropped from three thousand milliseconds to around one hundred and eighty, comfortably under our SLA. I verified the output was identical before and after, because a faster but wrong statement would be far worse than a slow correct one, especially with money.

The reason I tell it this way is that performance work lives or dies on discipline. I always measure before I change anything, because intuition about performance is unreliable and it is easy to waste days optimizing code that was never the bottleneck. I fix the specific thing the profiler points to, verify with real before-and-after numbers, and I am honest about any trade-off.

Remember one sentence: Measure first, fix the real bottleneck the profiler shows you, and prove the improvement with before-and-after numbers.

---

Share
Use ← → arrow keys to navigate