PhonePe Software Engineer — Interview Questions

PhonePe Software Engineer Interview Questions

PhonePe processes UPI transactions at national scale, which means engineers here deal with problems most companies never see: idempotency at massive volume, latency budgets measured in milliseconds because a slow payment feels broken to a user, and failure modes where "just retry" can mean double-charging someone. This guide covers the kind of system design and debugging questions that come up specifically because of what PhonePe's infrastructure has to guarantee.

PhonePe's Interview Process for Software Engineer

Typically 4–5 rounds: an online assessment or DSA screen, one or two system design rounds, a hands-on coding round, and a bar-raiser or hiring manager round. System design rounds lean heavily toward payments-specific constraints — consistency, idempotency, and failure recovery — rather than generic "design Twitter" style prompts.


Question 1: Handling a 10x Traffic Spike

Design a system component that can absorb a 10x spike in UPI transaction volume during a flash sale, without increasing the payment failure rate. Assume your current system is provisioned for normal daily peak load. Walk through your approach.

Why interviewers ask this

This tests whether a candidate reaches for the obvious answer — "just autoscale" — or actually reasons about what breaks first under 10x load in a payments system specifically: database write contention, downstream bank/NPCI rate limits, and queueing behavior under backpressure.

Example strong answer

"Autoscaling compute is necessary but not sufficient here, because the bottleneck in a UPI flow usually isn't my own service — it's downstream: the bank's PSP, NPCI's switch, or a shared database that every payment write touches. Scaling my API layer 10x doesn't help if the queue backs up at a system I don't control.

So first, I'd separate the request path into a fast synchronous part — validate the request, check idempotency key, return an acknowledgment — and an asynchronous part that actually processes the transaction against the bank rail, using a message queue to smooth out bursts instead of hitting the downstream system at raw request rate. That converts a 10x request spike into a controlled, rate-limited processing pipeline, rather than a 10x spike hitting NPCI directly.

For the database, I'd check whether the current write path is a single hot table — like a transactions table keyed by user ID — because that's where lock contention shows up first under load. If so, I'd look at sharding by a high-cardinality key, or at minimum moving to append-only writes with async reconciliation instead of synchronous read-modify-write locks on account balances.

Idempotency is non-negotiable at 10x load, because retries from client timeouts will spike too — every request needs an idempotency key checked before any downstream call, so a retried request never double-processes even if the original request is still in flight.

For monitoring, I'd want real-time visibility into queue depth and downstream latency specifically, not just CPU/memory — because at 10x load, the failure signal shows up as growing queue lag long before it shows up as server errors, and I want alerting to catch that early enough to shed load gracefully — for example, degrading to a 'processing, we'll confirm shortly' UX for a subset of traffic rather than hard-failing requests."

Follow-up questions

  • The downstream bank's PSP itself is rate-limited and can't be scaled. How do you prevent your queue from growing unbounded?
  • How would you test this design's behavior at 10x load before a real flash sale, without risking real transactions?

Question 2: The 3% Success-Rate Drop

After a deploy this morning, payment success rate has dropped from 99.2% to 96.1%. Nothing else has changed — no bank outages reported, no infra alerts firing. How do you find the root cause?

Why interviewers ask this

This tests structured debugging under ambiguity — whether a candidate narrows the search space methodically instead of guessing, and whether they think about correlation with the deploy before chasing unrelated leads.

Example strong answer

"A 3-point drop right after a deploy, with no external outage, points me toward the deploy itself as the prior hypothesis I test first — not because it's definitely the cause, but because ruling it in or out fast narrows the search space the most.

First, I'd segment the failures: are they concentrated on one bank, one payment mode — UPI collect vs. UPI intent, say — one app version, or one region? If failures are isolated to a specific bank or payment mode that happens to touch code the deploy changed, that's close to confirmed. If failures are spread evenly across everything, the deploy is less likely to be the direct cause, and I'd widen the search to shared infrastructure — a config change, a certificate rotation, a dependency version bump that shipped alongside the deploy without being flagged as risky.

I'd pull the diff for today's deploy and specifically check anything touching the payment initiation or callback-handling path, since that's where a subtle bug — like a changed timeout value, or an altered retry condition — could silently increase failures without throwing hard errors. I'd also check error codes, not just the failure rate: are these client timeouts, bank declines, or internal 5xxs? Each points somewhere different — timeouts suggest a latency regression from the deploy, internal 5xxs suggest a code bug, bank declines suggest something about how we're now formatting requests to the bank.

If I can reproduce the failure pattern in a staging environment with production-like traffic, I'd confirm the fix there before a hotfix rollout — but if the pattern is clearly isolated and severe, I wouldn't wait for full reproduction before rolling back the deploy, since payment failures compound in cost the longer they run at scale."

Follow-up questions

  • The failures are spread evenly, not isolated to the new deploy's code paths. What's your next hypothesis?
  • How do you build monitoring so this kind of regression is caught in the first five minutes after deploy, not discovered hours later?

Question 3: Preventing Double-Debits on Retry

A user's payment request times out on their end — their app shows a spinner, then an error — but the transaction actually succeeded on the backend. They retry. How do you design the system so this retry can never result in a double-debit?

Why interviewers ask this

Idempotency is a foundational concept in payments engineering, and PhonePe wants to see whether the candidate treats it as a first-class design constraint, not an afterthought bolted on after a bug is found in production.

Example strong answer

"Every payment initiation request needs a client-generated idempotency key — typically a UUID generated once per user action, not per network attempt — that's included in the original request and every retry of that same logical transaction. On the backend, before processing any payment, I check whether that idempotency key has already been seen.

If it has, I don't reprocess — I look up the stored result of the original request and return that response directly, whether it was a success, failure, or still-processing state. This means a retry is safe by construction: it either returns the confirmed outcome of the first attempt, or, if the original request is still in flight, it should return a 'still processing, don't retry yet' response rather than kicking off a second attempt in parallel.

The tricky part is the 'still in flight' case — if the client retries while the original request is mid-processing, a naive idempotency check might not find a stored result yet and could let a second processing attempt start. I'd handle this with a status field on the idempotency record: PENDING, SUCCESS, or FAILED, set to PENDING atomically the moment the first request starts. Any retry that finds a PENDING status gets told to wait and poll, not to proceed — this needs to be an atomic check-and-set at the database level, using a unique constraint on the idempotency key so two near-simultaneous requests can't both pass the check.

I'd also make sure idempotency keys expire on a defined window — say 24 hours — long enough to cover realistic retry behavior, but not indefinite, since storing every key forever isn't necessary and the business logic around 'is this actually still the same user action' gets fuzzier over very long windows."

Follow-up questions

  • What happens if the client generates a new idempotency key on every retry instead of reusing the original one — how do you defend against that?
  • How would you reconcile a case where the idempotency record says SUCCESS but the downstream bank ledger shows no matching transaction?

Question 4: Regional Load for Pincode

Pincode, PhonePe's hyperlocal commerce product, sees sharp traffic spikes in specific pin codes during local events — a festival in one city, a cricket match crowd in another. How would you design the backend to handle these localized, unpredictable spikes without over-provisioning capacity everywhere all the time?

Why interviewers ask this

This checks whether a candidate can reason about geographically partitioned load, a less common pattern than generic global traffic spikes, and whether they know how to apply targeted elasticity instead of blanket over-provisioning.

Example strong answer

"The key insight is that the spikes are geographically localized, so the fix should be too — over-provisioning the entire system for a spike that only ever hits one region at a time wastes money and doesn't actually solve the problem well, because a truly regional spike can still overwhelm a shared, non-partitioned resource.

I'd look at partitioning the system by region — either through geographically-aware routing at the load balancer level, or through sharding data stores by pin code cluster — so a spike in one city's traffic scales that partition's resources without needing to scale the whole fleet. Combined with autoscaling policies set at the partition level rather than globally, this lets capacity respond to where the load actually is.

For genuinely unpredictable spikes — a cricket match isn't always known in advance — I'd want fast-reacting autoscaling triggers based on request rate and queue depth per partition, with pre-warmed capacity buffers sized based on historical spike patterns rather than reacting purely reactively, since cold-start latency on new instances can be too slow for a spike that ramps in minutes.

I'd also separate read-heavy operations — browsing local listings, checking availability — from write-heavy operations — placing an order — since read traffic can be served from cache or read replicas far more cheaply during a spike, while write traffic needs the more careful, consistency-focused path. If most of the spike traffic is browsing rather than transacting, aggressive caching alone might absorb most of the load without touching the write path at all.

Finally, I'd build in graceful degradation — if a specific partition is genuinely overwhelmed beyond its scaled capacity, showing a slightly stale cached view of availability is a much better user experience than a failed page load or a failed order."

Follow-up questions

  • How do you decide the boundary for a 'partition' — by pin code, by city, by some other geographic unit — and what are the trade-offs?
  • A spike happens in a region with no historical data to base a pre-warmed buffer on. What do you do differently?

Question 5: Latency vs. Consistency in the Ledger

Your team is designing the internal ledger service that records every transaction's final state. Product wants transaction status to update instantly in the app the moment a payment completes. Your infra lead is worried that prioritizing instant updates could risk showing users an inconsistent balance if a write hasn't fully propagated. How do you resolve this trade-off?

Why interviewers ask this

This is a classic CAP-adjacent trade-off, but grounded in a real payments UX problem — PhonePe wants to see whether a candidate can reason about which parts of the system need strong consistency and which can tolerate eventual consistency, rather than treating the whole system as one monolithic consistency decision.

Example strong answer

"I wouldn't treat this as one system-wide consistency decision — different parts of the flow have different requirements. The ledger record that determines whether money actually moved needs to be strongly consistent; that's the source of truth, and it must be written and confirmed before we consider a transaction final. There's no acceptable trade-off there — showing a 'success' status before the ledger write is durably confirmed risks telling a user their payment succeeded when it might not have.

But the app's displayed transaction history or balance view doesn't need to read from that strongly-consistent write path directly. I'd have the ledger write synchronously to the source-of-truth store, then asynchronously propagate to a read-optimized view — a cache or read replica — that the app's UI actually queries. This gets the app close to instant updates in the vast majority of cases, since propagation lag is typically milliseconds, while keeping the actual financial record strongly consistent.

For the edge case where a user checks their transaction status in the tiny window before propagation completes, I'd show a 'processing' state rather than a stale 'pending' or, worse, an incorrect 'failed' — the UI should reflect genuine uncertainty rather than guess. Once propagation confirms, the status updates to the true final state.

I'd also make sure the async propagation path has its own monitoring for lag — if propagation ever falls meaningfully behind the write path, that's worth alerting on, because a growing gap between 'money moved' and 'user can see it moved' erodes trust even if the underlying ledger is correct the whole time."

Follow-up questions

  • How would you handle a scenario where the async propagation fails entirely for a subset of transactions — how do you detect and reconcile that?
  • Product pushes back and says even a few hundred milliseconds of 'processing' state hurts the user experience. How do you respond?

Preparation tip

PhonePe's engineering questions almost always have a hidden constraint baked in — money can't be lost or duplicated, and failures happen at a scale where "rare" edge cases occur constantly. The strongest answers explicitly name that constraint early, rather than designing a generic scalable system and bolting payments correctness on as an afterthought.