How to Pass the PhonePe SDE-2 Machine Coding Round

AAcePrompt Team·September 9, 2026·10 min read
How to Pass the PhonePe SDE-2 Machine Coding Round

PhonePe's machine coding round has a reputation. You have a strict 90 to 120-minute timer to prove you can write clean, extensible, and thread-safe code. Forget standard algorithmic puzzles on a whiteboard; this interview demands a working, production-like system built completely from scratch, without leaning on external frameworks like Spring or Guava. If you are interviewing for a Software Development Engineer 2 (SDE-2) role, you will likely face one of their most rigorously evaluated problems: designing a concurrent, multi-level in-memory cache. The evaluators are not just looking for code that compiles. They expect rock-solid object-oriented principles, the right data structures for eviction, and fine-grained concurrency control that can handle millions of simulated requests. Let us break down exactly how to architect, write, and test this system so you hit PhonePe's high engineering bar.

The PhonePe SDE-2 Interview Process

Before writing a single line of code, you need to understand where this round fits into the broader hiring pipeline. The machine coding round is usually the first major technical hurdle, acting as a massive filter for candidates who struggle to translate high-level concepts into executable code under pressure.

RoundFocus AreaDuration
1. Machine CodingLow-Level Design, Concurrency, Clean Code90-120 mins
2. Problem SolvingData Structures, Algorithms, Complexity60 mins
3. System DesignHigh-Level Architecture, Scalability60 mins
4. Hiring ManagerBehavioral, Past Projects, Culture Fit60 mins

The Exact Problem Statement

When the timer starts, you will receive a document outlining the requirements. You are tasked with building a multi-level cache system. You must support an arbitrary number of cache levels (L1, L2, L3, up to Ln). Each level will have its own configurable capacity and its own configurable eviction policy. You need to implement standard cache operations: a get method to retrieve a value by its key, and a put method to insert or update a key-value pair. Furthermore, the system must be highly concurrent. Multiple threads will read and write to the cache simultaneously, and your code must guarantee data consistency without resorting to a single global lock that destroys throughput. Finally, you must write a driver program that demonstrates your cache working under a multi-threaded load.

The 90-Minute Execution Strategy

How to Pass the PhonePe SDE-2 Machine Coding Round

Time management is the single biggest point of failure in this round. Many candidates spend 45 minutes drawing UML diagrams and run out of time to implement thread safety. You need a ruthless execution plan. Spend the first 15 minutes defining your interfaces and clarifying assumptions with the evaluator. Do you need to support generic types for keys and values? (Yes, you should). Are null values allowed? (Usually no, clarify this). Spend the next 40 minutes implementing the single-threaded core logic: the core data structures, the eviction policies, and the multi-level orchestration. Dedicate 20 minutes to upgrading the system to be thread-safe using advanced locking mechanisms. Reserve the final 15 minutes for writing the multi-threaded driver class and fixing any edge cases. Do not deviate from this timeline.

Deconstructing the Multi-Level Cache Problem

Designing the core entities and interfaces

A solid Low-Level Design starts with decoupling responsibilities. Do not cram your storage logic and eviction logic into a single God class. First, define a CacheProvider interface featuring your standard get, put, and delete methods. Next, define an EvictionPolicy interface. This interface needs two crucial methods: keyAccessed(K key) to track usage, and evictKey() which returns the key that was removed. Separating these concerns allows you to use the Strategy Pattern. By injecting the EvictionPolicy into your cache level, you can effortlessly swap out LRU (Least Recently Used) for LFU (Least Frequently Used) later without rewriting the core cache logic.

For your core entities, you will build a CacheLevel class. This class acts as a facade for a single tier of the cache. It holds a Map for actual data storage, a reference to its specific EvictionPolicy, an integer for its maximum capacity, and a string identifier (like 'L1' or 'L2'). Finally, you need a MultiLevelCache class to manage a List of those CacheLevel objects. When it comes to managing the multiple levels themselves, the Chain of Responsibility pattern fits perfectly. A read request hits Level 1; if you get a miss, the MultiLevelCache orchestrates propagating that request right down the chain to Level 2, and so on.

Building the LRU Eviction Policy

LRU is the default expectation for cache eviction. To achieve O(1) time complexity for both access updates and evictions, you must combine two data structures: a HashMap and a Doubly Linked List. The HashMap stores the cache keys mapped to the corresponding Node in the Doubly Linked List. The Doubly Linked List maintains the temporal ordering of accesses. When a key is accessed via the keyAccessed method, you look up its Node in the HashMap, sever its current connections in the list, and move it to the head of the list (representing the most recently used item).

When the cache reaches capacity and you need to insert a new item, you call evictKey. This method looks at the tail of the Doubly Linked List, removes that tail node, deletes the corresponding entry from the HashMap, and returns the evicted key so the CacheLevel can remove the actual data from its storage. You must write this Doubly Linked List from scratch. Using Java's LinkedHashMap is a massive red flag because it completely bypasses the data structure evaluation of the interview.

Implementing read and write propagation

Your cache's performance lives and dies by its read and write mechanics. Let us look at a read operation first. You query L1. If you hit a cache miss, you move on to query L2. When L2 actually holds the data, you return it to the user, but you also need to asynchronously or synchronously promote that data back up to L1 to speed up future reads. If L1 happens to be at capacity, promoting the data will trigger an eviction in L1. What happens to the evicted L1 data? In a standard multi-level cache, you simply drop it, because that data already exists in L2 or lower. However, you must clarify this exact behavior with your interviewer.

Write operations usually mean choosing between a Write-Through, Write-Back, or Write-Around policy. During a high-pressure interview, a synchronous Write-Through to all levels is much easier to implement and explain, though you absolutely need to discuss the trade-offs with your evaluator. Write-Through guarantees high data consistency across every level, but it comes with a latency penalty because the operation blocks until all levels are updated. Write-Back is faster but risks data loss if the system crashes before the data is persisted to lower levels. Stick to Write-Through for the code, but explain Write-Back verbally.

Ensuring thread-safety without killing performance

Concurrency is exactly where most candidates trip up and lose the SDE-2 offer. Slapping a single synchronized keyword onto your cache methods is a terrible idea because it creates a massive bottleneck; only one thread can read or write at a time. You need fine-grained locking instead. Using a ConcurrentHashMap for the storage layer is a decent starting point, but it will not maintain the strict ordering you need for your LRU eviction policy, which relies on your custom Doubly Linked List. Updating the HashMap and the Doubly Linked List must be an atomic operation.

The fix here is to use a ReentrantReadWriteLock at the CacheLevel. This lock allows multiple threads to grab the read lock simultaneously during cache hits, maximizing read throughput. When a cache miss happens, or when you add a new key, you release the read lock and acquire the write lock to safely update both the HashMap and the Doubly Linked List together. You must handle the double-checked locking scenario here: by the time a thread acquires the write lock after a cache miss, another thread might have already fetched the data and put it into the cache. Always check the cache storage one more time after acquiring the write lock before doing the expensive fetch operation.

If you want to push for even higher performance and secure a strong hire rating, bring up lock striping. Instead of one lock for the entire CacheLevel, maintain an array of 16 locks. When a request comes in, you hash the key, modulo it by 16, and acquire only the lock for that specific segment. This allows up to 16 concurrent writes to completely different segments of the cache, drastically reducing lock contention.

Tip: Always write a robust driver class with a Main method that spawns multiple threads to concurrently read and write to your cache. Proving live during the evaluation that your code will not throw ConcurrentModificationExceptions or deadlock is a huge green flag for the interviewer.

Handling Extensibility Follow-Ups

If you finish the core implementation with 15 minutes to spare, the interviewer will test your architecture by asking for new features. Because you used interfaces and the Strategy Pattern, you are well-prepared. They might ask how you would implement a Time-To-Live (TTL) feature where keys expire after a certain duration. You can explain two approaches: passive expiration (checking the timestamp only when a key is accessed and deleting it if expired) and active expiration (running a background daemon thread that periodically sweeps and removes expired keys).

They might also ask how you would support a Least Frequently Used (LFU) policy. You can explain that you would create a new class implementing your EvictionPolicy interface. Instead of a Doubly Linked List, this class would use a Min-Heap based on access frequency, or a HashMap of frequencies mapped to Doubly Linked Lists (to achieve O(1) LFU operations). Because your CacheLevel depends on the EvictionPolicy interface, you would not need to change a single line of the CacheLevel code to support this.

Proving It Works: The Multi-Threaded Driver

Writing the code is only half the battle; you have to prove it works under load. Create a driver class with a main method. Do not just write sequential get and put calls. Instantiate an ExecutorService with a fixed thread pool of 10 threads. Use a CountDownLatch initialized to 1. Have all 10 threads wait on the latch, and then count down the latch in the main thread to unleash all threads onto your cache simultaneously. Have half the threads writing random keys and the other half reading them. Use AtomicInteger to track successful reads and writes. This setup proves to the evaluator that your locking mechanism actually works and prevents race conditions under sudden spikes of concurrent traffic.

Common Failure Modes in PhonePe's Evaluation

Passing the PhonePe SDE-2 machine coding round takes a tricky balance of raw speed, architectural foresight, and a deep understanding of concurrency. Many candidates fail because they write 'God classes' that mix storage, eviction, and multi-level routing into one massive 500-line file. Others fail because they use Thread.sleep() in their tests instead of proper concurrency primitives like CountDownLatch, showing a lack of real-world multi-threading experience.

When you structure your code with clean interfaces, handle race conditions using fine-grained locks, and prove it all works with a multi-threaded driver, you are showing the evaluator that you write real, production-ready code. If you are looking to practice these exact scenarios and get real-time feedback, AcePrompt can actually listen to your mock interviews. It provides instant architectural suggestions to help you sharpen your Low-Level Design skills when the pressure is on. Master the ReentrantReadWriteLock, separate your eviction policies, and practice building that Doubly Linked List until it becomes muscle memory. Good luck.

Frequently asked questions

How long is the PhonePe machine coding round?

You will typically have 90 to 120 minutes. Within that tight window, you are expected to understand the core requirements, design a clean class structure, implement the logic, and deliver a fully working executable that handles multi-threaded traffic.

Can I use external libraries like Guava or Spring?

No, you cannot. PhonePe expects you to build all the core data structures and concurrency controls entirely from scratch, relying only on the standard library of your chosen programming language.

What eviction policies are usually asked?

LRU (Least Recently Used) is the standard expectation for this round. That said, interviewers will likely ask you to design the system flexibly enough so that an LFU (Least Frequently Used) or Time-To-Live (TTL) policy can be plugged in without heavy refactoring.

Is thread safety mandatory for PhonePe SDE-2?

Absolutely. Handling concurrency properly is one of the biggest differentiators between SDE-1 and SDE-2 candidates at PhonePe. You have to prove you can handle race conditions gracefully using advanced locking mechanisms.

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 machine coding round with real-time AI guidance.

Get started

See pricing →

Keep reading

PhonePe SDE-2 Machine Coding Round: Multi-Level Cache