Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,050visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsMachine Coding Exercises
✓ FreeIntermediate· 7 min read

Food Delivery Order Matching

Order/DeliveryPartner/Restaurant/MatchingEngine classes, a nearest-partner Strategy implementation, and how this class design plugs into the HLD geospatial matching from Uber.

Published September 23, 2026


Food Delivery Order Matching

Core classes

class Order { Restaurant restaurant; List<OrderLine> lines; Location deliveryLocation; DeliveryPartner assignedPartner; }
class DeliveryPartner { String id; Location currentLocation; boolean available; }
class Restaurant { Location location; }

interface MatchingStrategy {
    Optional<DeliveryPartner> findPartner(Order order, List<DeliveryPartner> availablePartners);
}

Advertisement

Nearest-partner matching as an injectable Strategy

class NearestPartnerStrategy implements MatchingStrategy {
    public Optional<DeliveryPartner> findPartner(Order order, List<DeliveryPartner> availablePartners) {
        return availablePartners.stream()
            .filter(p -> p.available)
            .min(Comparator.comparingDouble(p -> distance(p.currentLocation, order.restaurant.location)));
    }
    private double distance(Location a, Location b) { /* haversine or simple Euclidean for LLD-scope */ return 0.0; }
}

class MatchingEngine {
    private final MatchingStrategy strategy; // swappable — nearest-partner today, utilization-balanced tomorrow

    Optional<DeliveryPartner> match(Order order, List<DeliveryPartner> availablePartners) {
        return strategy.findPartner(order, availablePartners);
    }
}

Same Strategy shape as every matching/allocation problem in this course (Parking Lot's spot allocation, Elevator System's dispatch strategy) — MatchingEngine never hard-codes "nearest wins," it delegates to whichever MatchingStrategy it's configured with.

Plugging into HLD-level geospatial matching

This LLD design's distance() calculation and linear availablePartners scan are appropriately simplified for a class-design exercise — at real scale, finding nearby available partners efficiently is exactly the geospatial indexing problem covered in Design a Ride-Sharing System (Redis GEO/geohash, or a quadtree), not a linear scan over every partner in the system. The connection point is direct: MatchingEngine.match()'s availablePartners parameter, in a real system, would already be a pre-filtered, geospatially-narrowed candidate list (the output of a Redis GEO radius query) rather than the full partner roster — the LLD class design and the HLD geospatial infrastructure compose cleanly, each responsible for a different part of the same problem: HLD narrows "who's nearby," LLD decides "which of the nearby ones actually gets this order."

Offer, accept, reject: the assignment lifecycle

Choosing a partner is only half of matching. The partner must accept, and while an offer is pending, the same partner mustn't receive a second one. Model the lifecycle explicitly:

enum AssignmentStatus { OFFERED, ACCEPTED, REJECTED, EXPIRED }

class Assignment {
    final Order order; final DeliveryPartner partner; final Instant offeredAt;
    AssignmentStatus status = AssignmentStatus.OFFERED;
    Assignment(Order order, DeliveryPartner partner, Instant offeredAt) {
        this.order = order; this.partner = partner; this.offeredAt = offeredAt;
    }
}

class DispatchService {
    private final MatchingEngine engine;
    private final PartnerDirectory directory;                 // supplies nearby available partners
    private final Clock clock;
    private final Duration offerTimeout = Duration.ofSeconds(30);
    private final int maxAttempts = 5;

    DispatchService(MatchingEngine engine, PartnerDirectory directory, Clock clock) {
        this.engine = engine; this.directory = directory; this.clock = clock;
    }

    Optional<Assignment> dispatch(Order order) {
        Set<String> excluded = new HashSet<>();
        for (int attempt = 0; attempt < maxAttempts; attempt++) {
            List<DeliveryPartner> candidates = directory.availableNear(order.restaurant.location).stream()
                    .filter(p -> !excluded.contains(p.id))
                    .toList();
            Optional<DeliveryPartner> chosen = engine.match(order, candidates);
            if (chosen.isEmpty()) return Optional.empty();              // nobody nearby: widen radius or queue

            DeliveryPartner partner = chosen.get();
            if (!directory.tryReserve(partner.id)) {                    // lost a race with another order
                excluded.add(partner.id);
                continue;
            }
            Assignment a = new Assignment(order, partner, clock.instant());
            AssignmentStatus answer = offerAndWait(a, offerTimeout);    // push notification + wait for reply
            if (answer == AssignmentStatus.ACCEPTED) {
                a.status = AssignmentStatus.ACCEPTED;
                order.assignedPartner = partner;
                return Optional.of(a);
            }
            directory.release(partner.id);                              // rejected or timed out
            excluded.add(partner.id);
        }
        return Optional.empty();                                         // escalate: surge pricing, manual dispatch
    }
}

Key design decisions:

  • tryReserve is the concurrency guard. Two orders at the same restaurant can both see the same nearest partner. An atomic reserve (compare-and-set on available, or a conditional database or Redis update) makes one of them lose, and that order simply tries the next candidate.
  • The exclusion set prevents re-offering to a partner who just declined this order.
  • Bounded attempts and a timeout mean an order never waits forever. The fallback (widen the search radius, raise the delivery fee, alert operations) is a product decision, and it's explicit in the code.
  • The MatchingEngine and its strategies stay pure: given candidates, pick one. Reservation, offers and retries are the dispatch service's job. Separating choosing from committing keeps each part testable.

A scoring strategy instead of pure distance

Real matching balances several factors. A weighted-score strategy keeps them in one place, and stays a drop-in replacement:

class WeightedScoreStrategy implements MatchingStrategy {
    private final double wPickup = 1.0, wDetour = 0.5, wIdle = 0.2;

    public Optional<DeliveryPartner> findPartner(Order order, List<DeliveryPartner> candidates) {
        return candidates.stream().min(Comparator.comparingDouble(p -> score(p, order)));
    }

    private double score(DeliveryPartner p, Order o) {
        double pickupKm = Geo.km(p.currentLocation, o.restaurant.location);
        double detourKm = p.currentDestination == null ? 0 : Geo.detourKm(p, o);   // batching with an existing trip
        double idleMinutes = p.idleMinutes();
        return wPickup * pickupKm + wDetour * detourKm - wIdle * idleMinutes;     // lower is better
    }
}

The weights become configuration, which you can tune, or learn from data later, without touching the dispatch flow.

Follow-up questions this topic invites — and their answers

Q: Why does MatchingEngine take availablePartners as a parameter rather than owning/querying the full partner list itself? A: This keeps MatchingEngine decoupled from wherever partner location data actually lives (an in-memory list here, a Redis GEO index in production) — the caller (which does know how to efficiently fetch nearby available partners) supplies the candidate set, and MatchingEngine's only job is choosing among them, matching the Dependency Inversion principle of depending on what's passed in, not reaching out to a specific data source itself.

Q: How would utilization-balancing (from Design a Ride-Sharing System's matching tradeoffs) be implemented as an alternative MatchingStrategy here? A: A UtilizationBalancedStrategy implementing the same interface, factoring in each candidate partner's recent delivery count or idle time alongside distance — zero changes needed to MatchingEngine or Order, exactly demonstrating the Strategy pattern's swap-without-touching-the-caller benefit in practice, not just in theory.

Q: What happens if the matched partner rejects the delivery offer? A: The matching flow needs a retry loop — similar to Design a Ride-Sharing System's 'offer to nearest, if rejected offer to next' pattern — calling match() again with the rejecting partner excluded from availablePartners, rather than treating a single match() call as guaranteed-final.

Q: Should Restaurant's location factor into partner selection, or should delivery destination matter too? A: A more complete matching strategy would weigh BOTH proximity to the restaurant (for pickup) and the delivery destination (to avoid, e.g., sending a partner already heading the opposite direction) — the simplified NearestPartnerStrategy shown only considers restaurant proximity, which is a reasonable starting scope to state explicitly before optionally extending.

Q: How do you prevent two orders from being assigned the same partner at the same moment? A: Make reservation atomic: a compare-and-set on the partner's availability (an in-memory AtomicBoolean, a conditional database update, or Redis SET NX). The matcher can suggest the same partner to two orders, but only one reservation succeeds, and the other order moves to its next candidate.

Q: Why separate MatchingStrategy (choosing) from DispatchService (committing)? A: Choosing is a pure function of the candidates, which is easy to unit test and swap. Committing involves concurrency, timeouts, notifications and retries. Mixing them would make every new strategy re-implement the fragile parts.

Q: How would you batch two orders from the same restaurant onto one partner? A: Allow partners with an active trip to be candidates, and score the extra detour and delay that the new order would add to the existing one. Only assign if both orders stay within their promised delivery times. The assignment then holds a list of orders rather than one.

Advertisement

Previous

Restaurant Management System

Next

Design a Shopping Cart & Checkout Flow

AI Tutor

Lesson: Food Delivery Order Matching

Quick actions

AI responses can be inaccurate. Verify critical information.