issue 117apr 27mmxxvi
est. 2017
Sun, 27 Apr 2026
vol. IX · no. 117
PapersAdda
placement intelligence, since 2017
640+ briefs · 24 campuses · by reservation
verified offers · sourced from r/developersIndia
razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1

System Design: Parking Lot LLD 2026 [Complete OOD with Code]

10 min read
Uncategorized
Updated: 8 Jun 2026
Aditya Sharma
Aditya's Edit

PapersAdda 2026 Placement Cycle

By Aditya Sharma·Founder & Editor, PapersAdda

What changed in 2026 drives

Mass-recruiter offer letters are flatter for 2026 batch - the 4-5 LPA ASE band has barely budged in three years while inflation eats real wages. Premium tracks (Digital, Pro, Elite, Specialist) are still where the differential lives, and they are entirely test-driven. If you are aiming higher than the default offer, the coding round is not optional pageantry - it is the entire interview.

What I'd actually study for this

  • 01Two solid coding-round answers (1 medium-hard DSA each, with edge-case discussion) > five half-baked ones
  • 02One real project you can defend end-to-end - file paths, design decisions, and what you would change
  • 03One DBMS schema you actually built (not a textbook ER diagram), with at least 3 join-heavy queries written from memory
  • 04Three behavioural STAR stories: failure recovered, conflict handled, ownership taken

Where most candidates trip up

The single biggest mistake is treating company-specific guides as primary prep and DSA as secondary. It is the opposite. Mass recruiters use the test as a filter, but premium tracks at every IT services company use coding to allocate offer band. Spend 70% of prep time on DSA + system fundamentals, 20% on company-specific patterns, 10% on HR rehearsal. Reverse that ratio and you collect the default offer.

Editorial commentary by Aditya Sharma · written for PapersAdda · not generated, not aggregated.

Last Updated: June 2026


Why Parking Lot is the Most Common LLD Problem

Candidates report parking lot as the most frequently asked LLD problem, appearing in roughly 25-30% of LLD rounds at Amazon, Flipkart, Paytm, and Goldman Sachs. Based on public preparation resources and candidate-reported interview threads, it tests class hierarchy design, SOLID adherence, concurrency awareness, and design pattern recognition in a single question.


Requirements Gathering

Functional requirements:

  • Multiple floors, multiple spots per floor
  • Spot types: Motorcycle, Compact, Large, Handicapped
  • Vehicle types: Motorcycle, Car, SUV/Truck
  • Issue ticket on vehicle entry (spot assignment)
  • Accept payment on exit, compute fee based on duration
  • Display real-time availability per floor

Non-functional requirements:

  • Thread-safe: multiple entry/exit gates running concurrently
  • Extensible: new spot types, vehicle types, pricing models without major refactoring
  • Consistency: no two vehicles assigned the same spot

Scale and Capacity Assumptions

Before designing classes, state your scale assumptions out loud in the interview. They shape your concurrency and storage choices.

Single large lot:
  Floors: 6
  Spots per floor: 400
  Total spots: 2,400

Entry/exit gates: 8 (4 entry, 4 exit) operating concurrently
Peak arrival rate: 1 vehicle per gate per 10 seconds = 0.4 vehicles/sec
Average parking duration: 90 minutes
Daily throughput: roughly 6,000 to 8,000 vehicles

Ticket record size:
  ticket_id (UUID): 16 bytes
  vehicle plate: 12 bytes
  spot_id: 8 bytes
  entry/exit timestamps: 16 bytes
  fee: 8 bytes
  Total: ~60 bytes per ticket

Daily ticket storage: 8,000 * 60 = ~480 KB/day
Yearly: ~175 MB (trivial; a single relational DB table suffices)

The takeaway: a single parking lot is a low-throughput system. The hard part is not storage or QPS, it is correctness under concurrency (no double-assigned spots) and extensibility (new vehicle types, new pricing). This is why the problem is graded as LLD (object design) rather than HLD (distributed systems). If the interviewer scales it to a nationwide chain of 10,000 lots, the storage and a central availability service become relevant, which is covered in the follow-up section.


Class Hierarchy Design

ParkingLot (Singleton)
  |-- List<Floor>
  |-- PricingStrategy (injected)
  |-- DisplayBoard

Floor
  |-- List<ParkingSpot>
  |-- ReentrantLock (thread safety)

ParkingSpot (abstract)
  |-- MotorcycleSpot
  |-- CompactSpot
  |-- LargeSpot
  |-- HandicappedSpot

Vehicle (abstract)
  |-- Motorcycle
  |-- Car
  |-- Truck

Ticket
  |-- vehicle, spot, entry_time, exit_time

PricingStrategy (interface)
  |-- FlatRatePricing
  |-- HourlyPricing

PaymentProcessor
  |-- Cash, Card, UPI

DisplayBoard
  |-- shows_available_count per floor

Full Implementation

from enum import Enum
from datetime import datetime
import threading
import uuid
from abc import ABC, abstractmethod


class SpotType(Enum):
    MOTORCYCLE = 1
    COMPACT = 2
    LARGE = 3
    HANDICAPPED = 4


class VehicleType(Enum):
    MOTORCYCLE = 1
    CAR = 2
    TRUCK = 3


class Vehicle(ABC):
    def __init__(self, license_plate, vehicle_type):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    @property
    @abstractmethod
    def compatible_spots(self):
        pass

class Motorcycle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, VehicleType.MOTORCYCLE)

    @property
    def compatible_spots(self):
        return [SpotType.MOTORCYCLE, SpotType.COMPACT]

class Car(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, VehicleType.CAR)

    @property
    def compatible_spots(self):
        return [SpotType.COMPACT, SpotType.LARGE]

class Truck(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, VehicleType.TRUCK)

    @property
    def compatible_spots(self):
        return [SpotType.LARGE]


class ParkingSpot:
    def __init__(self, spot_id, spot_type, floor_number):
        self.spot_id = spot_id
        self.spot_type = spot_type
        self.floor_number = floor_number
        self.is_available = True
        self.vehicle = None

    def assign(self, vehicle):
        """Assign vehicle to this spot."""
        if not self.is_available:
            raise Exception(f"Spot {self.spot_id} is already occupied")
        self.vehicle = vehicle
        self.is_available = False

    def vacate(self):
        """Remove vehicle from spot."""
        self.vehicle = None
        self.is_available = True

    def can_fit(self, vehicle):
        return self.is_available and self.spot_type in vehicle.compatible_spots


class PricingStrategy(ABC):
    @abstractmethod
    def calculate_fee(self, duration_minutes, vehicle_type):
        pass

class HourlyPricing(PricingStrategy):
    RATES = {
        VehicleType.MOTORCYCLE: 10,
        VehicleType.CAR: 30,
        VehicleType.TRUCK: 60
    }

    def calculate_fee(self, duration_minutes, vehicle_type):
        hours = max(1, (duration_minutes + 59) // 60)  # ceil to nearest hour
        return hours * self.RATES.get(vehicle_type, 30)

class FlatRatePricing(PricingStrategy):
    RATES = {
        VehicleType.MOTORCYCLE: 50,
        VehicleType.CAR: 100,
        VehicleType.TRUCK: 200
    }

    def calculate_fee(self, duration_minutes, vehicle_type):
        return self.RATES.get(vehicle_type, 100)


class Ticket:
    def __init__(self, vehicle, spot):
        self.ticket_id = str(uuid.uuid4())[:8]
        self.vehicle = vehicle
        self.spot = spot
        self.entry_time = datetime.now()
        self.exit_time = None
        self.fee = 0

    @property
    def duration_minutes(self):
        end = self.exit_time or datetime.now()
        return int((end - self.entry_time).total_seconds() / 60)


class Floor:
    def __init__(self, floor_number, spots):
        self.floor_number = floor_number
        self.spots = spots
        self._lock = threading.Lock()

    def available_count(self):
        return sum(1 for s in self.spots if s.is_available)

    def find_and_assign_spot(self, vehicle):
        """Atomic: find available spot + assign. Returns spot or None."""
        with self._lock:
            for spot in self.spots:
                if spot.can_fit(vehicle):
                    spot.assign(vehicle)
                    return spot
        return None


class DisplayBoard:
    def __init__(self, floors):
        self.floors = floors

    def show(self):
        for floor in self.floors:
            print(f"Floor {floor.floor_number}: {floor.available_count()} spots available")


class ParkingLot:
    """
    Singleton. Manages multiple floors and issues/validates tickets.
    """
    _instance = None
    _init_lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._init_lock:
                if not cls._instance:
                    cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, pricing_strategy=None):
        if hasattr(self, '_initialized'):
            return
        self.floors = []
        self.active_tickets = {}  # ticket_id -> Ticket
        self.pricing = pricing_strategy or HourlyPricing()
        self.display_board = None
        self._initialized = True

    def add_floor(self, floor):
        self.floors.append(floor)
        self.display_board = DisplayBoard(self.floors)

    def park_vehicle(self, vehicle):
        """Entry: find spot, issue ticket."""
        for floor in self.floors:
            spot = floor.find_and_assign_spot(vehicle)
            if spot:
                ticket = Ticket(vehicle, spot)
                self.active_tickets[ticket.ticket_id] = ticket
                print(f"Parked {vehicle.license_plate} at floor {spot.floor_number}, spot {spot.spot_id}")
                print(f"Ticket ID: {ticket.ticket_id}")
                return ticket

        print("Parking lot is full")
        return None

    def exit_vehicle(self, ticket_id):
        """Exit: validate ticket, compute fee, free spot."""
        ticket = self.active_tickets.get(ticket_id)
        if not ticket:
            raise Exception(f"Invalid ticket: {ticket_id}")

        ticket.exit_time = datetime.now()
        ticket.fee = self.pricing.calculate_fee(
            ticket.duration_minutes,
            ticket.vehicle.vehicle_type
        )

        ticket.spot.vacate()
        del self.active_tickets[ticket_id]

        print(f"Vehicle {ticket.vehicle.license_plate} exited. Fee: Rs. {ticket.fee}")
        return ticket.fee

Usage Example

# Create parking lot with 2 floors
lot = ParkingLot(pricing_strategy=HourlyPricing())

floor1_spots = [
    ParkingSpot('F1-M1', SpotType.MOTORCYCLE, 1),
    ParkingSpot('F1-M2', SpotType.MOTORCYCLE, 1),
    ParkingSpot('F1-C1', SpotType.COMPACT, 1),
    ParkingSpot('F1-C2', SpotType.COMPACT, 1),
    ParkingSpot('F1-L1', SpotType.LARGE, 1),
]
lot.add_floor(Floor(1, floor1_spots))

# Park vehicles
car = Car("KA01AB1234")
truck = Truck("MH12CD5678")
bike = Motorcycle("DL3EF9012")

t1 = lot.park_vehicle(car)
t2 = lot.park_vehicle(truck)
t3 = lot.park_vehicle(bike)

lot.display_board.show()

# Exit
import time
time.sleep(1)
lot.exit_vehicle(t1.ticket_id)

SOLID Adherence Analysis

SOLID PrincipleHow this design satisfies it
Single ResponsibilityParkingLot manages entry/exit; PricingStrategy handles fees; DisplayBoard handles UI
Open/ClosedNew vehicle or spot type: add a class. New pricing: implement PricingStrategy. No existing classes modified.
Liskov SubstitutionCar, Truck, Motorcycle all safely substitutable for Vehicle
Interface SegregationPricingStrategy has only one method: calculate_fee
Dependency InversionParkingLot depends on PricingStrategy (abstraction), not HourlyPricing (concrete)

Concurrency Design

The floor-level lock ensures that finding and assigning a spot is atomic. Without this, two concurrent entry requests could both see the same spot as available and assign it to two vehicles. The lock is per-floor rather than per-lot: vehicles entering on floor 1 do not block vehicles entering on floor 2.

For a multi-gate entry system at very high scale, a spot reservation queue (add spot_id to a Redis set, pop to claim) replaces the in-memory lock, enabling distributed concurrency across multiple gate processes. The Redis SPOP operation is atomic, so two gate processes can never claim the same spot id even when they execute simultaneously, which is exactly the guarantee the in-memory floor lock provides within a single process. This is the standard upgrade path interviewers want to hear when they push the design from one process to many.


Payment Processing Design

Payment is the second extensibility axis interviewers probe. The wrong approach is a giant if-else on payment method inside the exit flow. The right approach is the Strategy pattern again, mirroring pricing.

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        """Return a PaymentResult (success/failure + transaction id)."""
        pass

class CashPayment(PaymentMethod):
    def pay(self, amount):
        return PaymentResult(success=True, txn_id=str(uuid.uuid4()), method="CASH")

class CardPayment(PaymentMethod):
    def __init__(self, gateway):
        self.gateway = gateway   # injected payment gateway client

    def pay(self, amount):
        resp = self.gateway.charge(amount)
        return PaymentResult(success=resp.ok, txn_id=resp.id, method="CARD")

class UpiPayment(PaymentMethod):
    def __init__(self, upi_client):
        self.upi_client = upi_client

    def pay(self, amount):
        resp = self.upi_client.collect(amount)
        return PaymentResult(success=resp.ok, txn_id=resp.ref, method="UPI")

The exit flow accepts an injected PaymentMethod and never branches on type. Adding a new method (wallet, NFC tag) is a new class, not an edit to existing code. This is the Open/Closed Principle applied a second time, and calling that out explicitly scores points.

For card and UPI, the gateway call can fail or time out. The exit flow must handle a failed payment by keeping the spot occupied and the ticket active, then surfacing a retry prompt. Never free the spot before payment confirmation, otherwise a vehicle could leave without paying and the spot would be marked free prematurely.


State Transitions for a Parking Spot

Modeling the spot as an explicit state machine prevents a class of bugs. A spot moves through AVAILABLE, then RESERVED (optional), then OCCUPIED, then back to AVAILABLE on vacate. Illegal transitions (OCCUPIED directly to RESERVED, or vacating an already-AVAILABLE spot) should raise rather than silently corrupt state.

AVAILABLE  --reserve-->  RESERVED  --assign-->  OCCUPIED
AVAILABLE  --assign---->  OCCUPIED
RESERVED   --expire(15min)-->  AVAILABLE
OCCUPIED   --vacate----->  AVAILABLE

Encoding this as a guarded method set (reserve, assign, vacate, expire) keeps the invariant "a spot is in exactly one state" enforced in one place. When the interviewer asks "what if two threads call assign on the same spot," the answer is: the floor lock serializes the find-and-assign, and the spot's assign method rejects a second call because the state is no longer AVAILABLE.


Follow-up Questions and Answers

How do you support reservation (pre-booking)? Add a ReservationService that holds a spot for a user for 15 minutes. Spot status gets a RESERVED state in addition to AVAILABLE and OCCUPIED. A background sweeper expires reservations past their TTL and returns spots to AVAILABLE. The reservation must be atomic with the spot lookup, otherwise two users could reserve the same spot.

How do you support multiple parking lot locations (chain)? Add a ParkingLotChain that holds multiple ParkingLot instances. The chain's find_spot() delegates to each lot. Each lot is still a singleton within its process. At nationwide scale (10,000 lots), the in-memory singleton breaks down: availability moves to a central service backed by Redis, where each lot publishes its live free-spot counts and the chain queries the cache rather than each lot directly.

How do you add a subscription model (monthly pass)? Add a MembershipStrategy implementing PricingStrategy that checks the vehicle's membership tier and applies a flat monthly rate or free parking logic. Membership lookup happens at exit time before fee calculation.

How do you handle a lost ticket? Charge a fixed maximum daily rate. The system looks up the active ticket by license plate (which requires an index on plate, not just ticket_id) and applies a lost-ticket penalty fee defined in the pricing strategy.

How do you find the nearest available spot to an entrance? Precompute the distance from each entrance to each spot. Maintain a min-heap of available spots keyed by distance per entrance. On assign, pop the nearest; on vacate, push back. This turns the find operation into O(log n) instead of a linear scan, which matters for a 2,400-spot lot at peak.

How do you make the DisplayBoard update in real time across gates? The DisplayBoard subscribes to spot state-change events (Observer pattern). Each assign/vacate publishes an event, and the board decrements or increments the per-floor available count. This avoids re-scanning all spots on every display refresh.


Spot Assignment Algorithm: Nearest Available vs Compact Best Fit

Two strategies for assigning spots to vehicles. Nearest available: scan spots in order, assign the first one that fits the vehicle type. Fast, O(1) with a queue per type. Compact best fit: assign the smallest spot that fits the vehicle (a motorcycle gets a motorcycle spot before a compact spot). This maximizes utilization when large spots are scarce. In most LLD interviews, nearest available is sufficient; mention best fit as an optimization if the interviewer asks about utilization.


Methodology applied to this articlelast verified 8 Jun 2026
Sources used
Public exam-pattern documents, official recruiter pages, and verified candidate reports on r/developersIndia and LinkedIn.
Verification window
Page last edited 8 Jun 2026 by Aditya Sharma. Numbers and patterns sanity-checked against the most recent 2026 cycle drives we tracked.
What we did NOT do
  • No fabricated salary numbers or success rates. If we quote a range, it's sourced.
  • No noun-substituted templates. This article was not generated by swapping company names in a stock prompt.
  • No paid placements, sponsored coaching links, or affiliate-shilled course pushes.
Verification policy: /editorial-standards/. Found something incorrect? Submit a correction - we respond within 48 hours.

Explore this topic cluster

More resources in Uncategorized

Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.

Paid contributor programme

Sat this this year? Share your story, earn ₹500.

First-person experience reports help future candidates prep smarter. We pay verified contributors ₹500 via UPI per accepted story - with byline.

Submit your story →

Ready to practice?

Take a free timed mock test

Put what you learned into practice. Our mock tests match the 2026 pattern with timer, navigator, reveal, and score breakdown. No signup.

Start Free Mock Test →

More from PapersAdda

Share this guide: