Razorpay Lead SDE Machine Coding interview
I went through the machine coding round at Razorpay for a Lead Software Engineer position and wanted to share my experience. Hope this gives you a clear picture of what to expect.
Round: Machine Coding Round (Virtual)
Duration: 2 Hours
Platform: I was allowed to use my own local IDE and share my screen. The interviewer was present throughout, observing and occasionally asking questions.
The Problem Statement
The problem was to design and implement a persistent (in-memory) Pub/Sub queue system. The requirements were very clear and provided upfront:
Implement a persistent (in memory) pub-sub queue mechanism with guaranteed delivery of every published message for all the subscribed consumers in that subscribed topic in the same order.
Features:
- Expose topics.
- Publisher to push messages against a topic.
- Subscribe and unsubscribe from a topic.
- Subscriber to consume from a topic. A topic can have multiple subscribers.
- Maintain the state of consumption of each message in each topic for each consumer.
- Maintain order of message consumption for each consumer.
- Filtered message consumption in a topic by the consumer.
- Handle cases of consumers not being available. What to do with those messages?
My Approach and Thought Process (First 20-25 mins)
This is the most critical part. Before writing a single line of code, I dedicated time to clarifying requirements and laying out a solid design.
1. Requirement Clarification (Asking the "Right" Questions):
I started by asking clarifying questions to define the scope:
- Message Format: "What is the expected format of a message? Is it a simple string, or a structured object with metadata?" (Interviewer said a string payload is fine, but metadata for filtering is a good idea).
- Filtering Logic: "For filtered consumption, what's the complexity? Should it be simple key-value matching on metadata, or something more complex like regex or custom functions?" (Interviewer suggested starting with simple key-value matching).
- Subscriber Unavailability: "For requirement #8, if a consumer is unavailable, should its messages be held indefinitely, or is there a TTL or memory constraint I should consider?" (Interviewer confirmed that for this round, holding them indefinitely in memory is the expected behavior, ensuring guaranteed delivery).
- Concurrency: "Should I assume a single-threaded environment, or do I need to make the system thread-safe?" (Interviewer said to start with a single-threaded implementation but be prepared to discuss concurrency).
2. Low-Level Design (LLD) on a Scratchpad:
I sketched out the main components and data structures. Communicating this design to the interviewer is key.
Message: A simplestructto hold a payload (std::string) and metadata (std::map<std::string, std::string>).Topic: The core component. I decided it should manage its own state.std::vector<Message> messages: To store all messages in the order they were published. Avectorprovides O(1) access by index, which is perfect for our consumption logic.std::unordered_map<SubscriberId, size_t> subscriber_offsets: This was the crux of the solution for features #5, #6, and #8. It maps each subscriber's ID to the index (offset) of the next message they are due to receive. This elegantly handles individual consumption state and ensures that an unavailable consumer can pick up right where they left off.std::unordered_map<SubscriberId, Filter> subscriber_filters: To store the filter criteria for each subscriber on this topic.
PubSubService: The main service class acting as the entry point.std::unordered_map<TopicId, Topic> topics_: A map to hold all created topics for fast lookup.- This class would expose the public API (
createTopic,publish,subscribe,consume, etc.).
I explicitly chose unordered_map for lookups due to its average O(1) time complexity, which is ideal for performance.
The Coding Phase (Approx. 80-90 mins)
My strategy was to build a working, simple solution first, and then layer on complexity if time permitted. I chose to implement the simpler version we discussed, initially skipping the filtering to ensure the core pub/sub logic was flawless.
-
File Structure: I created three files to demonstrate modularity:
pubsub.h,pubsub.cpp, andmain.cpp. This keeps the design clean and is good practice for a senior role. -
Core Logic Implementation:
publish(topicId, payload): This was straightforward. Find the topic in the map andpush_backthe new message into the topic'smessagesvector.subscribe(subscriberId, topicId): Find the topic. Add thesubscriberIdto thesubscriber_offsetsmap. The initial offset was set totopic.messages.size(), meaning the subscriber will only get new messages published after they subscribe.consume(subscriberId, topicId): This is the most important method.- Find the topic and locate the subscriber's current
offsetin thesubscriber_offsetsmap. - Check if
offset < topic.messages.size(). - If yes, retrieve the message at that index.
- Crucially, increment the subscriber's offset (
offset++) in the map. - Return the message.
- If no, it means the subscriber is up-to-date, so return an empty/null value (
std::optionalis great for this).
- Find the topic and locate the subscriber's current
-
Driver Code (
main.cpp): I spent about 10-15 minutes writing a comprehensivemainfunction. Do not skip this step! It proves your code works. I created multiple topics and subscribers, published messages, and had each subscriber consume messages to demonstrate:- Correct ordering.
- That each subscriber maintains their own independent progress.
- That an unsubscribed user no longer receives messages.
- Basic error handling (e.g., publishing to a non-existent topic).
Follow-up Questions & Discussion (Final 15 mins)
After I demonstrated the working solution, the interviewer moved to conceptual questions to test the depth of my knowledge.
-
Concurrency: "How would you make this thread-safe?"
- Answer: Explained that a single global mutex is bad for performance. A better approach is a more granular lock, like a
std::mutexinside eachTopicobject. Any operation modifying a topic (publish, subscribe) would lock that specific topic's mutex. This allows different topics to be modified concurrently.
- Answer: Explained that a single global mutex is bad for performance. A better approach is a more granular lock, like a
-
Scalability & Memory: "What happens if a topic has millions of messages and one subscriber is offline for a week? What's the problem here?"
- Answer: The
messagesvector in the topic would grow indefinitely, leading to an Out-Of-Memory error. I suggested solutions:- Garbage Collection: Track the minimum offset across all subscribers for a topic. Messages with an index less than this minimum can be safely deleted from the front of the vector (or moved to cold storage). This requires a
std::dequeor a ring buffer for better performance on front deletions. - TTL (Time-To-Live): Configure topics or messages to expire after a certain time.
- Backpressure/Quotas: Limit the number of unacknowledged messages a subscriber can have.
- Garbage Collection: Track the minimum offset across all subscribers for a topic. Messages with an index less than this minimum can be safely deleted from the front of the vector (or moved to cold storage). This requires a
- Answer: The
-
Guaranteed Delivery: "Your
consumemethod returns a message and moves on. What if the consumer crashes after consuming but before processing? The message is lost."- Answer: This was a great question. I explained that true guaranteed delivery requires a two-phase approach:
consume(): Fetches the message but does not advance the offset. It just marks the message as "in-flight."acknowledge(messageId): A new API method where the consumer confirms it has successfully processed the message. Only upon receiving anackdoes the system advance the subscriber's offset. This prevents message loss.
- Answer: This was a great question. I explained that true guaranteed delivery requires a two-phase approach:
Final Thoughts & Tips
- Communicate Everything: Talk through your design, your data structure choices, and your implementation strategy. The interviewer is evaluating your thought process as much as your code.
- Start Simple, Then Elaborate: Don't try to build the most complex, all-features-included solution from the start. Get a solid, working core done first. Then, show you've thought about the complex parts by discussing them.
- Code Quality Matters: Use clear variable names, keep functions small, and structure your code logically. For a senior role, this is non-negotiable.
- Test Your Code: A working solution with a solid test driver (
main) is infinitely better than a "theoretically perfect" but incomplete or buggy one.
It was a challenging but very fair and practical round that accurately simulated a real-world engineering task. I was informed a couple of days later that I had cleared the round.
Good luck to everyone interviewing! I hope this detailed breakdown helps you prepare.


