Library
27 min read

Design a parking lot

Fifty minutes into the round, your code works. A car comes in, gets a spot, and pays on the way out. Then the interviewer says: "Weekends are a flat ₹150 a day now. Show me what changes."

If the honest answer is "most of it", the round is lost, and it was lost in minute ten, when you chose your classes. The parking lot is the most common first LLD question because it is small enough to finish and still has three decisions that separate a working answer from a good one: which spot a car gets, what happens when two gates ask at once, and what the fee method is allowed to know.

The picture to keep
The cloakroom token

A cloakroom does not care what your coat is made of. It cares whether it fits on a hook or needs the big rail, which hook is free, and how long you left it. The token is the only thing that connects the coat going in with the coat coming out. A parking lot is the same machine with bigger coats.

In one line: Hand something in, get a token, hand the token back. Everything else is two rules you should expect to change: where it goes, and what it costs.

What this round expects#

LLD rounds come in two shapes, and how much code you write depends on which one you are in. Ask at the start if nobody tells you.

Design round on a whiteboardMachine coding round
TimeUsually 45 to 60 minutesUsually 90 minutes to 2 hours
What you produceThe class diagram, the key interfaces, one flow walked through, the one hard method in code or pseudocodeCode that compiles and runs, with a small demo at the bottom. In memory storage, no UI
What gets scoredWhere you drew the lines between classes, and whether you can defend each oneThe same, plus whether it runs, whether it reads well, and how fast a follow up goes in
On this pageSteps 1 and 2, then Change itAll of it. The code in Step 3 is the whole program, about 130 lines

Either way, the order of work is the same, and it is the order of this page: agree the requirements, find the objects, draw how they connect, then write code from the top down.

Step 1: Understand the problem#

Five minutes here saves twenty later. Each question below settles one line of the design, and the last two are cuts.

You askThey sayWhat it settles
Which vehicles, and which spot sizes?Bikes, cars and trucks. Small, medium and large spots.Two unions, VehicleType and SpotSize, and one rule for what fits where: a vehicle fits its own size or anything bigger. A bike can take any spot, a truck only a large one.
One floor or several? How many entry gates?Three floors. Two entry gates and one exit.Floor becomes a class. Two gates means two requests for a spot at the same moment, so claiming a spot has to be a single step. That is deep dive 2.
How is the fee worked out?Per hour by vehicle type: ₹20 for a bike, ₹40 for a car, ₹100 for a truck. Part of an hour counts as an hour, and one hour is the minimum.A PricingPolicy interface rather than an if inside exit(). Pricing is the rule the business changes most often, and the interviewer is going to change it.
Which spot does a driver get?Your call.A SpotFinder interface. "Your call" is an invitation: name two rules, pick one, and say what each costs. That is deep dive 1.
Do we take the payment?No. The exit gate takes it. Tell it the amount.A cut. exit() returns a receipt with the amount, and there are no payment classes. Saying so out loud is worth more than drawing a PaymentProcessor nobody asked for.
Does anything have to survive a restart?Not for this round.A cut. Maps in memory. Name the interface you would put in front of a database later, and move on.
In scope
  • Park a vehicle in a spot that fits it, and give the driver a ticket. The entry flow. One call, enter().
  • Take the ticket back, work out the fee, free the spot. The exit flow. One call, exit().
  • Choose the spot by a rule that can be swapped. The first thing the interviewer will poke at.
  • Charge by a rule that can be swapped. The second thing.
The numbers you commit to
  • A new pricing rule or spot rule is a new class, not an edit to ParkingLot.
  • Two gates can never be handed the same spot.
  • Every rule can be tested without waiting for a real clock: the time is passed in.
Cut, and say so out loud
  • Payments, refunds and lost tickets.
  • Booking a spot in advance. It is a different question, closer to movie ticket booking.
  • A database. Everything lives in memory for the round.
  • Display boards. Not in the first version, and they come back as a follow up in Change it.

Step 2: Find the objects and how they connect#

Read the problem back, and mark it up#

Say the problem back to the interviewer as one paragraph, then go through it for nouns and verbs. Nouns become classes or fields. Verbs become methods. Some words turn out to be scenery. Tap each phrase below, and guess before you do.

Tap each underlined phrase0 of 13 found

A has several . Each floor has in three . come in through an , where the driver gets a and is . At the exit, the driver based on the and , and the again. Later, the owners want .

Before you tap, guess: which of these phrases deserve a class of their own, and which are only a field on something else?

The class diagram#

Figure 2 is what you would draw on the board, and it is the most important artefact of the round. Draw it before writing code, then talk through it box by box. If the round is whiteboard only, this figure and the interfaces under it are most of what you produce.

UML has six kinds of line, and an interviewer expects you to use the right one. This page uses five. Here is how to read them.

Implements. BestFit keeps the SpotFinder promise.
Composition. A Floor is made of spots. No floor, no spots.
Aggregation. The lot holds a finder it was handed. It lives on without the lot.
Association. A spot knows the vehicle in it.
Dependency. The lot creates Tickets but does not keep a field for one.
Figure 2. ParkingLot sits in the middle and owns its floors, which own their spots. The two rules that change, which spot and what it costs, hang off the sides as interfaces, each with its own implementations. Nothing in the middle column knows which implementation it was given.

Three things in it are deliberate, and each is a question you should expect.

Composition for floors and spots, aggregation for the rules. A floor does not exist without its lot, and a spot does not exist without its floor, so those are filled diamonds. The finder and the pricing policy are handed to the lot from outside, and the same BestFit could serve two lots, so those are hollow.

Ticket is a box, but not a class in the code. It is data with no behaviour. The diagram shows what the system knows about. The code decides how to write it down, and in TypeScript plain data is an interface.

No Bike, Car or Truck boxes. There is one Vehicle with a type. Subclasses would only be worth it if a truck behaved differently from a car, and in this problem the only difference is size, which is data.

Step 3: Write it, from the top down#

Write the code in the order of the diagram: the vocabulary first, then the objects that hold state, then the rules, then the lot that ties them together. Every block below runs, and each one builds on the one before it, so together they are the whole program.

The vocabulary#

ts
type VehicleType = 'bike' | 'car' | 'truck'
type SpotSize = 'small' | 'medium' | 'large'
 
interface Vehicle {
  readonly plate: string
  readonly type: VehicleType
}
 
// Sizes in order, so "fits" is a comparison.
const RANK: Record<SpotSize, number> = { small: 0, medium: 1, large: 2 }
 
// The smallest spot each vehicle fits in.
const NEEDS: Record<VehicleType, SpotSize> = {
  bike: 'small',
  car: 'medium',
  truck: 'large',
}
 
const car: Vehicle = { plate: 'KA01AB1234', type: 'car' }
const van: Vehicle = { plate: 'X', type: 'van' }
// Error: Type '"van"' is not assignable to type 'VehicleType'

Two Record tables carry the whole "what fits where" rule. That buys more than it looks. When the interviewer adds electric scooters, you add 'scooter' to the union, and NEEDS stops compiling until it has a row for scooters. The compiler hands you the list of places to update.

The objects that hold state#

ts
class ParkingSpot {
  private vehicle: Vehicle | null = null
 
  constructor(
    readonly id: string,
    readonly size: SpotSize,
    readonly distance: number,
  ) {}
 
  get isFree(): boolean {
    return this.vehicle === null
  }
 
  fits(v: Vehicle): boolean {
    return RANK[this.size] >= RANK[NEEDS[v.type]]
  }
 
  // Check and claim in one step, with nothing that can run in between.
  tryPark(v: Vehicle): boolean {
    if (!this.isFree || !this.fits(v)) return false
    this.vehicle = v
    return true
  }
 
  release(): void {
    this.vehicle = null
  }
}
 
class Floor {
  constructor(
    readonly level: number,
    readonly spots: readonly ParkingSpot[],
  ) {}
 
  freeCount(size: SpotSize): number {
    return this.spots.filter((s) => s.isFree && s.size === size).length
  }
}
 
const spot = new ParkingSpot('M1', 'medium', 12)
console.log(spot.tryPark(car)) // true
console.log(spot.tryPark(car)) // false
spot.vehicle = null // Error: Property 'vehicle' is private

The spot is the only thing that can change whether it is taken. There is no park() that trusts the caller to have checked isFree first, because a caller that forgets is how two cars end up in one spot. tryPark checks and claims together and tells you whether it worked.

The contracts#

If the round is a whiteboard round, this is the code you write in full. Everything else can be a signature and a sentence.

ts
interface Ticket {
  readonly id: string
  readonly plate: string
  readonly type: VehicleType
  readonly spotId: string
  readonly entryAt: Date
}
 
interface Receipt {
  readonly ticketId: string
  readonly minutes: number
  readonly amount: number
}
 
interface SpotFinder {
  find(spots: readonly ParkingSpot[], v: Vehicle): ParkingSpot | undefined
}
 
interface PricingPolicy {
  fee(ticket: Ticket, exitAt: Date): number
}

Look at what fee takes: the whole ticket and the exit time, not a number of minutes. That one choice is deep dive 3, and it is the reason the weekend follow up at the top of this page is a ten minute job instead of a rewrite.

The rules#

ts
const fitting = (spots: readonly ParkingSpot[], v: Vehicle) =>
  spots.filter((s) => s.isFree && s.fits(v))
 
class NearestFirst implements SpotFinder {
  find(spots: readonly ParkingSpot[], v: Vehicle): ParkingSpot | undefined {
    return fitting(spots, v).sort((a, b) => a.distance - b.distance)[0]
  }
}
 
class BestFit implements SpotFinder {
  find(spots: readonly ParkingSpot[], v: Vehicle): ParkingSpot | undefined {
    return fitting(spots, v).sort(
      (a, b) => RANK[a.size] - RANK[b.size] || a.distance - b.distance,
    )[0]
  }
}
 
const minutesBetween = (from: Date, to: Date): number =>
  Math.round((to.getTime() - from.getTime()) / 60_000)
 
class HourlyPricing implements PricingPolicy {
  constructor(private readonly perHour: Record<VehicleType, number>) {}
 
  fee(ticket: Ticket, exitAt: Date): number {
    const minutes = minutesBetween(ticket.entryAt, exitAt)
    const hours = Math.max(1, Math.ceil(minutes / 60))
    return hours * this.perHour[ticket.type]
  }
}

The lot#

ts
interface Parked {
  readonly ticket: Ticket
  readonly spot: ParkingSpot
}
 
class ParkingLot {
  private readonly active = new Map<string, Parked>()
  private issued = 0
 
  constructor(
    private readonly floors: readonly Floor[],
    private readonly finder: SpotFinder,
    private readonly pricing: PricingPolicy,
  ) {}
 
  enter(v: Vehicle, at: Date): Ticket | null {
    const spots = this.floors.flatMap((f) => f.spots)
    const spot = this.finder.find(spots, v)
    if (!spot || !spot.tryPark(v)) return null
    this.issued += 1
    const ticket: Ticket = {
      id: `T${this.issued}`,
      plate: v.plate,
      type: v.type,
      spotId: spot.id,
      entryAt: at,
    }
    this.active.set(ticket.id, { ticket, spot })
    return ticket
  }
 
  exit(ticketId: string, at: Date): Receipt {
    const parked = this.active.get(ticketId)
    if (!parked) throw new Error(`No vehicle is parked on ${ticketId}`)
    const amount = this.pricing.fee(parked.ticket, at)
    parked.spot.release()
    this.active.delete(ticketId)
    const minutes = minutesBetween(parked.ticket.entryAt, at)
    return { ticketId, minutes, amount }
  }
}

ParkingLot has no pricing rule and no spot rule in it. It finds, claims, records and releases, and asks its two interfaces everything else. Notice that enter returns null when the lot is full rather than throwing. A full lot on a Saturday is an ordinary outcome, and exceptions are for things that should not happen, like a ticket that was never issued.

Run it#

ts
const at = (time: string) => new Date(`2026-10-24T${time}:00`)
const pricing = new HourlyPricing({ bike: 20, car: 40, truck: 100 })
 
function openLot(finder: SpotFinder): ParkingLot {
  const ground = new Floor(0, [
    new ParkingSpot('L1', 'large', 4),
    new ParkingSpot('M1', 'medium', 12),
    new ParkingSpot('M2', 'medium', 16),
    new ParkingSpot('S1', 'small', 28),
  ])
  return new ParkingLot([ground], finder, pricing)
}
 
const truck: Vehicle = { plate: 'MH12XY9876', type: 'truck' }
 
const lot = openLot(new BestFit())
const t1 = lot.enter(car, at('10:00'))
const t2 = lot.enter(truck, at('10:05'))
console.log(t1?.spotId, t2?.spotId) // M1 L1
 
if (t1) console.log(lot.exit(t1.id, at('12:10')))
// > { ticketId: 'T1', minutes: 130, amount: 120 }
 
const greedy = openLot(new NearestFirst())
greedy.enter(car, at('10:00'))
console.log(greedy.enter(truck, at('10:05'))) // null

The last three lines are deep dive 1 in miniature. Same lot, same two vehicles, a different finder, and the truck is turned away while two medium spots stand empty.

Here are the two calls in order, with every object that takes part.

1/11 The gate passes in the vehicle and the time. The lot never asks the system clock, which is what lets a test say "three hours later" without waiting three hours.
Your answer

Two gates run enter() at the same moment, and both finders pick spot M1. What in the code above stops two cars being sent to M1, and would it still work if the lot ran on two servers?

Step 4: Deep dives#

Which spot: nearest first or best fit#

The obvious rule is the closest free spot that fits. It is also the rule that turns trucks away from a half empty lot. Large spots are usually the ones near the entrance, because trucks need room to turn, so a nearest first rule fills them with cars.

Try it below. Send two cars and then a truck with NearestFirst. Then empty the lot, switch to BestFit and send the same three.

SpotFinder10:00
entry→
Try this: two cars, then a truck, with NearestFirst. Empty the lot, switch to BestFit and send the same three. Click a parked vehicle to let it out and see the fee.

With NearestFirst, the two cars take L1 and L2 and the truck is turned away with four medium spots and two small ones empty. BestFit gives each vehicle the smallest spot it fits, nearest first among those, so the cars go to M1 and M2 and the truck gets L1.

Neither rule is right in general. BestFit makes car drivers walk further, and a mall that sees one truck a month may prefer that. The answer the interviewer wants is that this is a choice with a cost, that you picked one, and that because SpotFinder is an interface, changing your mind later is one new class and one line where the lot is built.

Two gates, one spot#

In Java or Go, two gates are two threads, and the race is easy to see. In TypeScript on Node.js it hides, because JavaScript runs one piece of synchronous code at a time. Nothing can run between two lines unless one of them is an await. So the race appears the moment someone adds one, for example to read the number plate from a camera between finding the spot and claiming it.

ts
const spots: (string | null)[] = [null, null]
const readPlate = () => Promise.resolve() // the gate camera
 
async function enter(plate: string): Promise<number> {
  const i = spots.findIndex((s) => s === null)
  await readPlate() // the other gate runs here
  spots[i] = plate
  return i
}
 
Promise.all([enter('KA01'), enter('MH12')]).then((got) => {
  console.log(got) // [ 0, 0 ]
  console.log(spots) // [ 'MH12', null ]
})

Both gates found spot 0 free, both waited for the camera, both wrote. The first car's claim was overwritten, and nothing failed. Move the await before the findIndex, or better, keep finding and claiming in one synchronous method, and the race is gone.

ts
const spots: (string | null)[] = [null, null]
const readPlate = () => Promise.resolve()
 
async function enter(plate: string): Promise<number> {
  await readPlate()
  const i = spots.findIndex((s) => s === null)
  spots[i] = plate // found and claimed with no await in between
  return i
}
 
Promise.all([enter('KA01'), enter('MH12')]).then((got) => {
  console.log(got) // [ 0, 1 ]
})

That is what tryPark is. And there is a second line of defence in enter: if a spot was taken between find and tryPark, tryPark says no, and the lot returns null instead of a ticket for a taken spot.

The same rule, check and claim in one step, has a different form at each scale.

Where the gates runWhat makes the claim one step
One Node.js processA synchronous method with no await inside, like tryPark
One Java or Go process, many threadsA lock around find and claim, per floor or per spot
Several servers, one databaseOne conditional update: set the plate WHERE id = 'M1' AND plate IS NULL, then check that one row changed

The last row is the same fix as seat inventory in IRCTC tatkal booking, and optimistic vs pessimistic locking covers the choice between the lock and the conditional update. You do not need to go there in an LLD round. You do need to say that the method that claims a spot is the one that has to be safe, and how.

What the fee method is allowed to know#

The first draft of this design, like most, had fee(type: VehicleType, minutes: number). It is shorter and it passes every test for hourly pricing. Then the follow ups arrive.

  • Weekend rates need the date. minutes does not have one.
  • Monthly pass holders park free. That needs the plate. type does not have one.
  • A night rate needs the time of entry. Gone too.

Each of those is a change to the interface, and so to every class that implements it and to the lot that calls it. fee(ticket, exitAt) passes the facts and lets each policy work out what it needs. A policy that only wants the duration computes it in one line.

Passing the answer instead of the facts

When an interface takes a value you already worked out, such as a duration, a total or a flag, every implementation is stuck with your idea of what matters. Pass the record the value came from. It costs nothing now, and it is the difference between a follow up being a new class and being an edit to four files.

Change it#

This is where the round is decided. The follow up in an LLD round is rarely "ten times the traffic". It is a new business rule, and what is scored is how much of your design has to move to take it. Pick a follow up and watch the diagram: green is new, amber is edited, faded is untouched.

The interviewer adds
“Saturday and Sunday are a flat ₹150 a day. Weekdays stay hourly.”
1 new class0 edited classes
One new class and no edits. WeekendPricing is a PricingPolicy that holds two others and picks one by the date on the ticket. It only works because fee() takes the whole ticket and the exit time. If fee() had taken a number of minutes, as the first draft of this design did, the class could not tell a Saturday from a Monday and the interface, HourlyPricing and ParkingLot would all have had to change.
Read every follow up as text

Weekend rates. Saturday and Sunday are a flat ₹150 a day. Weekdays stay hourly. One new class and no edits. WeekendPricing is a PricingPolicy that holds two others and picks one by the date on the ticket. It only works because fee() takes the whole ticket and the exit time. If fee() had taken a number of minutes, as the first draft of this design did, the class could not tell a Saturday from a Monday and the interface, HourlyPricing and ParkingLot would all have had to change.

Free spots board. Put a board at each entrance that shows how many spots are free on each floor. Observer. The lot keeps a list of listeners and tells each one after every enter() and exit(). That is one honest edit to ParkingLot, and it is the last: a mobile app, an SMS alert or a dashboard is another LotListener, and the lot never learns what any of them are.

EV chargers. Some spots now have chargers. An electric car should get one if one is free. This one reaches the core, and it should. "Has a charger" is a new fact about a spot and "is electric" a new fact about a vehicle, so those two classes gain a field each. The rule that uses the facts is a new SpotFinder that tries charger spots for electric cars and hands everything else to a fallback finder. ParkingLot does not change.

Five lots in a city. The company runs five lots now. Send a driver to the nearest one with space. A new class above the lot, and one small method on ParkingLot so the city can ask it a question. Nothing else moves. Had ParkingLot been a singleton, which is what many first drafts do, this follow up would be a rewrite, because the whole design would assume there is exactly one lot.

Three of the four follow ups add a class and edit at most one. The EV one edits two, because it adds new facts about spots and vehicles, and facts belong on the objects they describe. A good design still changes. What you want is for each change to land in the one place that owns it.

ParkingLot as a singleton

"There is only one parking lot" is the most common reason given for making it a singleton, and it is a requirement about today. Tests need a fresh lot each time, a second site needs a second lot, and the city follow up above needs five. Build one instance where the program starts and pass it to whoever needs it.

Date.now() inside exit()

If exit reads the clock itself, the only way to test a three hour stay is to wait three hours, or to fake the global clock. Passing the time in, as enter(v, at) and exit(id, at) do here, makes every pricing rule a plain function call in a test.

What the interviewer is scoring

Whether you asked before drawing. Whether each class has one reason to change. Whether the rules that vary sit behind interfaces, and whether you can say why those two and not others. Whether the method that claims a spot is safe with two gates. And when the follow up comes, whether you can point at the diagram and say "this is a new class here, and nothing else moves".

Trade-offs#

ChoiceWhat you gainWhat you payPick it when
BestFit over NearestFirstLarge spots stay free for the vehicles that need them, so trucks are not turned away from a lot with empty spots.Car drivers walk further, because the nearest spots are often the large ones.Mixed traffic with a real share of big vehicles. With almost no trucks, nearest first is the kinder rule.
A union for vehicle type, not subclassesOne Vehicle shape, and the compiler lists every table to update when a type is added.If vehicles later behave differently, not only in size, you will move to classes.While the only difference between vehicles is data. That is true for this whole question.
fee(ticket, exitAt) over fee(type, minutes)Weekend rates, passes and night rates are each one new class, with no edits.Every policy depends on the Ticket shape, so a change to Ticket touches them all.Almost always for a pricing rule, which is the part of any booking system that changes most.
enter() returns null when fullThe caller has to handle a full lot, and TypeScript will not let it forget.A null says nothing about why. Several reasons would want a result type instead.When there is one ordinary way to fail. If you need "full" and "already inside", return { ok: false, reason }.

Interview replay#

Interviewer
Design a parking lot.
Deliberately vague. The first test is whether you start drawing.
You
Before I draw anything, a few questions. Which vehicles and spot sizes, how many floors and gates, how the fee works, and do I handle payment? And is this a design discussion, or do you want running code?
The last question decides how the next 45 minutes are spent, and few candidates ask it.
Interviewer
Bikes, cars, trucks. Three floors, two entry gates. Hourly by type. Skip payment. Code would be good.
Two gates is a hint. They will come back to it.
You
Then I see ParkingLot, Floor, ParkingSpot, Vehicle and Ticket. Vehicle type and spot size are plain unions, because a truck does not behave differently from a car, it is only bigger. And two things here are rules the business will change: which spot a car gets and what it pays. I would put each behind an interface, SpotFinder and PricingPolicy, and pass them into the lot.
Names the classes, rejects the subclass trap with a reason, and finds the two Strategies before being asked.
Interviewer
Why not just a Vehicle base class with Car and Truck?
Checking whether the union was a reasoned choice or a shortcut.
You
Because they would have no method that differs. Subclasses earn their place when behaviour differs. Here the difference is which spot fits, which is a lookup in a table. If you later told me trucks need a weighbridge check at entry, that would be behaviour, and I would reconsider.
Says what would change the answer. That is what makes it an answer rather than a preference.
Interviewer
Both gates call enter at the same moment. Can two cars get the same spot?
The concurrency question, as expected.
You
Not with tryPark, because it checks and claims in one synchronous call, and in Node nothing runs in the middle of it. If someone added an await between find and claim, say for a camera, it would break, so the rule is no await inside the claim. If the gates were separate servers sharing a database, the claim becomes a conditional update on the spot row where the plate is null, and I check that one row changed.
Answers for the code on the board, then for the next scale up, without being asked.
Interviewer
The entry gate reads plates with a camera now. How does that fit?
An open question about something the candidate may never have worked with.
You
I have not worked with plate reading hardware, so I do not know how often it misreads or how slow it is. I would put it behind a PlateReader interface that the gate calls before enter, keep it out of the lot entirely, and ask you what should happen on a misread: let the car in on a manual ticket, or hold the barrier.
An honest "I do not know", followed by where it would go in the design and the question that matters. That scores.
Interviewer
Weekends are a flat ₹150 a day. What changes?
The follow up the whole design was built for.
You
One new class. WeekendPricing implements PricingPolicy, holds the weekday and weekend policies, and picks one from the entry date on the ticket. The lot is built with it instead of HourlyPricing. Nothing else changes, because fee already gets the whole ticket. If I had passed it a number of minutes, this would have been an interface change.
Points at the exact box, and names the earlier decision that made it cheap.
TypeScript you just picked up
type VehicleType = 'bike' | 'car' | 'truck'
A union of string literals. Only these three strings are allowed, and a typo does not compile.
Record<VehicleType, SpotSize>
An object with exactly one key per member of the union. Add a member and every Record over it needs a new row.
constructor(readonly id: string) {}
A parameter property. Declares the field, takes the argument and assigns it, in one line.
get isFree(): boolean
A getter. Read it like a field, spot.isFree, while it is worked out each time.
Vehicle | null
Either a vehicle or nothing. Strict mode makes you check for null before using it.
ParkingSpot | undefined
What find returns when no spot fits. The caller has to handle that case before calling tryPark.
new Map<string, Parked>()
A typed map. get() returns Parked | undefined, which is why exit checks it.
t1?.spotId
Optional chaining. If t1 is null, the whole expression is undefined instead of a crash.
60_000
A numeric separator. The underscore is only for reading, the value is 60000.
async function, await, Promise.all
An await is the only place another call can run in the middle of yours. Promise.all starts both and waits for both.

Checkpoint#

Checkpoint

1. With NearestFirst, two cars and then a truck arrive at a lot whose two large spots are nearest the entrance. What happens to the truck, and why?

2. Why does ParkingSpot have tryPark() instead of a park() that the lot calls after checking isFree?

3. Monthly pass holders now park free. With fee(ticket, exitAt), what is the smallest change?

Worth memorising
  • Two interfaces: SpotFinder and PricingPolicy. They are the two rules the business changes, so they are the two things you make swappable.
  • Check and claim in one step. No await inside it in Node, a lock in a threaded language, a conditional update in a database.
  • Pass time in. enter(v, at) and exit(id, at). A method that reads the clock cannot be tested for a three hour stay.
  • Pass facts, not answers. fee(ticket, exitAt), not fee(type, minutes), or every follow up becomes an interface change.
Say this in 60 seconds

I would ask about vehicles, floors, gates and pricing, and whether they want running code. Then the objects: a ParkingLot made of Floors made of ParkingSpots, a Vehicle with a type, and a Ticket that carries the plate, type, spot and entry time. Vehicle type and spot size are unions, not subclasses, because the only difference between a car and a truck here is size. The two rules the business will change, which spot a car gets and what it pays, go behind SpotFinder and PricingPolicy interfaces that the lot is given when it is built. I would use best fit, so cars do not fill the large spots trucks need, and say that it makes car drivers walk further. A spot claims itself with tryPark, which checks and claims in one step, so two gates cannot get the same spot, and across servers that becomes a conditional update. Time is passed in, so pricing is testable, and fee takes the whole ticket, so weekend rates or monthly passes are each one new class with nothing else edited.

Finished this one?Mark it complete to track your progress through Low level design.