Katkat

Build an E-Bike Rental Billing Dashboard With Rate Changes Over Time

RipplingSoftware Engineer · Technical Screen

Last updated

Design a billing dashboard for a bike-share fleet that prices each ride at the per-minute rate in effect when it started, supports rate changes that take effect at a future timestamp without repricing past rides, and reports an exact running total as a formatted string.

A city bike-share operator rents e-bikes by the minute and wants a single running number: the total amount billed across every ride recorded so far. Build the core of this billing dashboard as a class with four operations:

class EbikeBillingDashboard:
    def register_bike(self, bike_id, rate_per_minute): ...
    def record_ride(self, ride_id, bike_id, start_time, end_time): ...
    def update_rate(self, bike_id, new_rate, effective_at): ...
    def get_total_cost(self) -> str: ...
  • register_bike(bike_id, rate_per_minute) adds a bike with its starting per-minute rate in dollars.
  • record_ride(ride_id, bike_id, start_time, end_time) logs one completed ride.
  • update_rate(bike_id, new_rate, effective_at) changes a bike's rate starting at effective_at; rides that started before that moment keep being priced at whatever rate was in effect when they started.
  • get_total_cost() reports the running total of everything billed so far: a plain dollar figure, rounded to the cent and rendered with two digits after the decimal point, for example "4.50".

Before writing code, decide on and state the unit you'll use for timestamps (for example, integer Unix seconds). The interviewer is evaluating your object-oriented design as much as the final answer, and expects you to flag, unprompted, anywhere your interview-time solution would need to change for a real production system.

dashboard = EbikeBillingDashboard()
dashboard.register_bike("bike1", 0.20)             # $0.20/min
dashboard.record_ride("r1", "bike1", 0, 600)        # 10 min @ $0.20 = $2.00
dashboard.get_total_cost()                          # "2.00"
dashboard.update_rate("bike1", 0.30, effective_at=600)
dashboard.record_ride("r2", "bike1", 600, 900)      # 5 min @ $0.30 = $1.50
dashboard.get_total_cost()                          # "3.50"

Constraints and Clarifications

  • ride_id is globally unique. Submitting a ride whose id has already been recorded must be a no-op — it does not add to the total a second time.
  • A bike's rate can change multiple times over its lifetime; a ride is always priced using whichever rate was in effect at its own start_time, even if that ride is recorded after later rate changes have already been applied.
  • Assume a ride's duration is reasonably short (well under a day); you don't need to handle a ride that spans a rate change partway through — price the whole ride at the rate in effect when it started.
  • Everything lives in memory in a single process for this exercise.

Clarifying Questions

  • Should update_rate be allowed to insert an effective time earlier than rate changes already recorded, effectively rewriting history for rides not yet logged?
  • Is get_total_cost() called often enough that it needs to be cheaper than re-summing every ride each time?
  • Should amounts stay exact to the cent no matter how many rides and rate changes accumulate?

Part 1 — Core billing with a single rate per bike

Get register_bike, record_ride and get_total_cost working under the assumption that a bike's rate never changes after registration.

Part 2 — Rate history and idempotent recording

Add update_rate, make sure a ride is always priced at the rate effective at its own start time regardless of recording order, and make record_ride idempotent on ride_id.

Store, per bike, a sorted list of (effective time, rate) pairs rather than overwriting a single rate field, so you can ask "what rate applied at this exact moment in the past?"
If you track cost internally in a small-enough fractional unit, every per-ride charge lands on an exact integer. Save the single division back into dollars for the very last step, when you build the output string.

Hints

Hint 1

A bike's rate history is better modeled as a sorted list of (effective_at, rate) pairs than a single mutable field — that's what lets you answer 'what rate applied back then' correctly.

Hint 2

Binary search the rate history for the latest effective_at at or before a ride's start_time, rather than scanning linearly.

Hint 3

Keep a running total that updates by a fixed amount each time a ride is newly recorded, so get_total_cost never has to re-sum anything.

Hint 4

Decide what duration unit avoids fractional-cent rounding drift, and only convert to a dollar string at the very last step.

Follow-up questions

  • How would you support a full refund or adjustment for a ride already counted in the total, without just subtracting and risking a negative total from a bug elsewhere?
  • How would get_total_cost need to change if it had to report a total broken down per bike or per day, instead of one grand total?
  • What would you change if record_ride needed to be safe under concurrent calls from multiple threads for the same ride_id?
  • How would you extend the rate history to support a rate that itself varies by time of day, rather than a single value that only changes at discrete effective times?