Interview question for Machine coding round for Flipkart
Description
Implement the core system for Flipkart Minutes, Flipkart’s instant delivery platform, enabling customers to order any item for delivery within minutes. The system should efficiently manage customers, delivery partners, and orders, ensuring rapid and reliable fulfillment.
**Core Requirements **
Onboarding
● The system should support onboarding of new customers and delivery partners.
Order Placement & Cancellation
● Customers can place orders for any item (item name or ID is specified in the order). ● There are no in-stock/out-of-stock constraints; all items are always available. ● Customers can cancel orders if they have not yet been picked up by a delivery partner.
Order Assignment & Fulfillment
● Orders are auto-assigned to any available delivery partner.
● If no delivery partner is available, the order remains in a queue and is assigned as soon as a partner becomes free.
● Each delivery partner can handle only one order at a time.
● The number of ongoing orders can exceed the number of delivery partners (orders may queue).
● Delivery partners can pick up assigned orders and mark them as delivered. ● Canceled orders should not be assigned to a delivery partner
● If an assigned order gets canceled before pickup, the delivery partner should become available for other orders
● Once a delivery partner picks up an order, the order cannot be canceled by the customer or the system.
● Assume delivery partners are available 24x7. Ignore travel time.
Status Tracking
● The system should provide real-time status for orders and delivery partners.
Concurrency & Thread Safety
● The system must be thread-safe and handle all concurrency scenarios (multiple customers and partners acting simultaneously).
Bonus Features
● Notifications: Notify customers and delivery partners (via simulated logs) on order status changes.
● Ratings: Customers can rate delivery partners after successful delivery.
● Dashboard: Show top delivery partners based on number of deliveries and ratings. ● Auto-cancel: If no delivery partner picks up the order within 30 minutes of its creation,
the order should be canceled automatically regardless of whether an order has been assigned to a delivery partner or not
**Guidelines: **
● Time: 120 mins (Implementation).
● Write modular, clean and demo-able code (Test cases or runtime execution). ● A driver program/main class/test case is needed to test out the code by the evaluator with multiple test cases.
● Use design patterns wherever applicable.
● Please handle concurrency wherever applicable.
● Evaluation criteria: Demoable & functionally correct code, Code readability, Proper Entity modeling, Modularity & Extensibility, Separation of concerns, Abstractions, Exception Handling, Code comments.
● Code should handle edge cases properly and fail gracefully.
● You are not allowed to use any external databases like MySQL. Use only in-memory data structures.
● No need to create any UX or any HTTP API. It should be a standalone application. ● Usage of any AI powered tools such a chatGPT or github-copilot is strictly prohibited. You may use the internet to look up any syntactic references.
● You are free to use any popular programming language of your choice. ● The bonus features are optional. Attempt them only after finishing the p0 features. ● The problem may require you to make certain assumptions. Do make reasonable assumptions and convey them to the review panel.
Sample Test cases:
The input/output need not be in the same format this is for explaining the expected functionality i: input
o: output
● onboard customer - customer_id, name
● onboard delivery_partner - partner_id, name
● create order - customer_id, item_name ● cancel order - order_id
● show order status - (order_id)
● show delivery partner status - (partner_id) ● pick up order - (partner_id, order_id) ● complete order - (partner_id, order_id)
package org.example.exception;
public class InvalidOrderStatusException extends RuntimeException{
public InvalidOrderStatusException(String id,String msg){
super(msg+" | id :"+id);
}
}
package org.example.exception;
public class OrderException extends RuntimeException{
public OrderException(String id, String msg){
super(msg+" | id :"+id);
}
}
package org.example;
import org.example.models.Customer;
import org.example.models.DeliveryPartner;
import org.example.service.CustomerService;
import org.example.service.DashboardService;
import org.example.service.OrderService;
import org.example.service.PartnerService;
import java.util.List;
public class Main {
public static void main(String[] args) {
PartnerService partnerService = new PartnerService();
OrderService orderService = new OrderService(partnerService);
DashboardService dashboardService = new DashboardService();
DeliveryPartner p1 = DeliveryPartner.Builder.getBuilder()
.setId("P1")
.setName("name1").build();
DeliveryPartner p2 = DeliveryPartner.Builder.getBuilder()
.setId("P2")
.setName("name2").build();
partnerService.onboardPartner(p1);
partnerService.onboardPartner(p2);
Customer c1 = Customer.Builder.getBuilder().setId("C1")
.setName("cus1").build();
orderService.placeOrder("O1", c1);
orderService.placeOrder("O2", c1);
orderService.pickOrder("O1");
orderService.deliverOrder("O1", 5);
List<DeliveryPartner> top = dashboardService.topPartners(partnerService.getAllPartners());
}
}
package org.example.models;
public class Customer extends User{
private Customer(String id, String name){
super(id,name);
}
public static class Builder{
String name;
String id;
public static Builder getBuilder(){
return new Builder();
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Customer build(){
return new Customer(id, name);
}
}
}
package org.example.models;
import org.example.utility.UserUtils;
import java.util.concurrent.locks.ReentrantLock;
public class DeliveryPartner extends User{
private final ReentrantLock lock = new ReentrantLock();
double ratingSum=0.0;
int noOfDeliveries=0;
int ratingCount=0;
PartnerStatus status = PartnerStatus.AVAILABLE;
private DeliveryPartner(String id, String name){
super(id,name);
}
public boolean assignOrder(){
lock.lock();
try{
if(status.equals(PartnerStatus.AVAILABLE)){
this.status = PartnerStatus.IN_DELIVERY;
return true;
}
return false;
}
finally {
lock.unlock();
}
}
public void setAsAvailable(){
lock.lock();
try {
this.status = PartnerStatus.AVAILABLE;
}
finally {
lock.unlock();
}
}
public void recordDelivery(int rating){
lock.lock();
try{
noOfDeliveries++;
ratingSum += rating;
ratingCount++;
this.status = PartnerStatus.AVAILABLE;
}
finally {
lock.unlock();
}
}
public PartnerStatus getStatus() {
return status;
}
public double getRating() {
return UserUtils.getAvgRating(ratingSum, ratingCount);
}
public int getNoOfDeliveries() {
return noOfDeliveries;
}
public static class Builder{
String name;
String id;
public static DeliveryPartner.Builder getBuilder(){
return new DeliveryPartner.Builder();
}
public DeliveryPartner.Builder setName(String name) {
this.name = name;
return this;
}
public DeliveryPartner.Builder setId(String id) {
this.id = id;
return this;
}
public DeliveryPartner build(){
return new DeliveryPartner(id, name);
}
}
}
package org.example.models;
import org.example.exception.InvalidOrderStatusException;
import org.example.exception.OrderException;
import org.example.service.OrderStateChangeTracker;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class Order {
ReentrantLock lock = new ReentrantLock();
String id;
Customer customer;
DeliveryPartner partner;
OrderStatus orderStatus;
private List<OrderStateChangeTracker> trackersList = new ArrayList<>();
private Order(String id, Customer customer){
this.id=id;
this.customer = customer;
this.orderStatus = OrderStatus.CREATED;
}
public void assignPartner(DeliveryPartner partner){
lock.lock();
try{
if(!orderStatus.equals(OrderStatus.CREATED))
throw new InvalidOrderStatusException(id,"invalid order");
if(!partner.assignOrder()){
throw new OrderException(id, "partner not nassigned");
}
this.partner = partner;
orderStatus = OrderStatus.ASSIGNED;
notifyTrackers();
}
finally {
lock.unlock();
}
}
public void addTracker(OrderStateChangeTracker tracker){
trackersList.add(tracker);
}
private void notifyTrackers(){
for (OrderStateChangeTracker tracker : trackersList){
tracker.onStatusUpdate(this);
}
}
public void pickOrders(){
lock.lock();
try{
if(!orderStatus.equals(OrderStatus.ASSIGNED))
throw new InvalidOrderStatusException(id, "Order already picked");
orderStatus = OrderStatus.PICKED;
notifyTrackers();
}
finally {
lock.unlock();
}
}
public void cancelOrder(){
lock.lock();
try {
if(orderStatus.equals(OrderStatus.PICKED )|| orderStatus.equals(OrderStatus.DELIVERED ) )
throw new InvalidOrderStatusException(id, "Order cannot be canceled");
if (orderStatus.equals(OrderStatus.CANCELED))
return;
orderStatus = OrderStatus.CANCELED;
if(partner.getStatus().equals(PartnerStatus.IN_DELIVERY))
partner.setAsAvailable();
notifyTrackers();
}
finally {
lock.unlock();
}
}
public void deliver(int rating){
lock.lock();
try {
if(!orderStatus.equals(OrderStatus.PICKED))
throw new InvalidOrderStatusException(id, "order already delivered / canceled");
orderStatus = OrderStatus.DELIVERED;
partner.recordDelivery(rating);
notifyTrackers();
}
finally {
lock.unlock();
}
}
public String getId() {
return id;
}
public DeliveryPartner getPartner() {
return partner;
}
public OrderStatus getOrderStatus() {
return orderStatus;
}
public static class Builder{
String id;
Customer customer;
public static Builder getBuilder(){
return new Builder();
}
public Order build(){
return new Order(id, customer);
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setCustomer(Customer customer) {
this.customer = customer;
return this;
}
}
}
package org.example.models;
public enum OrderStatus {
CREATED,
ASSIGNED,
PICKED,
DELIVERED,
CANCELED
}
package org.example.models;
public enum PartnerStatus {
AVAILABLE,
IN_DELIVERY
}
package org.example.models;
public abstract class User {
String name;
String id;
User(String id, String name){
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
package org.example.service;
import org.example.models.Customer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CustomerService {
private final Map<String, Customer> customerMap = new ConcurrentHashMap<>();
public String onboard(Customer customer){
if(customerMap.containsKey(customer.getId()))
throw new IllegalStateException("Customer already found");
customerMap.put(customer.getId(), customer);
return customer.getId();
}
}
package org.example.service;
import org.example.models.DeliveryPartner;
import java.util.List;
public class DashboardService {
public List<DeliveryPartner> topPartners(List<DeliveryPartner> partners){
return partners.stream().sorted(
(a,b) ->
Integer.compare(b.getNoOfDeliveries(),a.getNoOfDeliveries())
).toList();
}
}
package org.example.service;
import org.example.exception.OrderException;
import org.example.models.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
public class OrderService {
private final int TIME_TO_AUTO_CANCEL = 30;
private final ReentrantLock lock = new ReentrantLock();
private final Map<String, Order> orderMap = new ConcurrentHashMap<>();
private final Queue<Order> orderQueue = new ConcurrentLinkedQueue<>();
private final ScheduledExecutorService schedular = Executors.newScheduledThreadPool(2);
private final PartnerService partnerService;
private final TrackingService trackingService = new TrackingService();
public OrderService(PartnerService partnerService){
this.partnerService = partnerService;
}
public Order placeOrder(String orderId, Customer customer){
if(orderMap.containsKey(orderId))
throw new IllegalStateException("Order Already present");
Order order = Order.Builder.getBuilder()
.setId(orderId)
.setCustomer(customer).build();
orderMap.put(orderId, order);
orderQueue.offer(order);
autoCancelIdDelayed(orderId);
assignOrders();
order.addTracker(trackingService);
return order;
}
public void assignOrders(){
lock.lock();
try {
for (DeliveryPartner partner : partnerService.getAllPartners()){
if(partner.getStatus().equals(PartnerStatus.IN_DELIVERY))
continue;
Order order = orderQueue.poll();
if(order == null)
break;
if(order.getOrderStatus().equals(OrderStatus.CANCELED))
continue;
try {
order.assignPartner(partner);
}
catch (OrderException e){
//log all partners busy
orderQueue.offer(order);
}
}
}
finally {
lock.unlock();
}
}
public void cancelOrder(String orderId){
Order order = orderMap.get(orderId);
order.cancelOrder();
assignOrders();
}
private void autoCancelIdDelayed(String orderID) {
schedular.schedule(() -> {
Order order = orderMap.get(orderID);
if (order == null)
return;
order.cancelOrder();
}, TIME_TO_AUTO_CANCEL, TimeUnit.SECONDS);
}
public void pickOrder(String orderId){
getOrder(orderId).pickOrders();
}
public void deliverOrder(String orderId, int rating){
getOrder(orderId).deliver(rating);
assignOrders();
}
private Order getOrder(String orderId){
Order order = orderMap.get(orderId);
if (order == null){
throw new OrderException(orderId, "order not found");
}
return order;
}
}
package org.example.service;
import org.example.models.Order;
public interface OrderStateChangeTracker {
void onStatusUpdate(Order order);
}
package org.example.service;
import org.example.models.DeliveryPartner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PartnerService {
private final Map<String, DeliveryPartner> partnersMap = new ConcurrentHashMap<>();
public String onboardPartner(DeliveryPartner partner){
if(partnersMap.containsKey(partner.getId()))
throw new IllegalStateException("Partner already available");
partnersMap.put(partner.getId(), partner);
return partner.getId();
}
public List<DeliveryPartner> getAllPartners(){
return new ArrayList<>(partnersMap.values());
}
}
package org.example.service;
import org.example.models.Order;
public class TrackingService implements OrderStateChangeTracker {
@Override
public void onStatusUpdate(Order order) {
System.out.println("Order tracking | id :"+order.getId()+" status: "+order.getOrderStatus());
}
}
package org.example.utility;
public class UserUtils {
public static double getAvgRating(double ratingSum, int ratingCount){
if(ratingCount == 0)
return 0.0;
return ratingSum/ratingCount;
}
}Any suggestions or issues in code are welcomed.
#Flipkart #Interview #OA