AI Infra Interviews logo
← Coding for Infra / 01
easy★ EssentialOpenAI

Given usage records of GPU allocations, compute what each tenant owes. Write it, and say what you would test.

Build a small decimal billing calculator with an explicit rounding policy, exact durations and tests for invalid records and credits.

Updated Sep 2026 · Learn how AI infrastructure works through explanations, worked examples and diagrams, then practise applying it to interview questions.

TL;DR: Agree on partial-hour billing and rounding before calculating a charge. Use decimal rates, validate each usage interval, and apply credits after summing the charges according to that policy.

How to approach it

Ask whether partial hours are proportional, where rounding occurs, and how billing periods and rate changes affect an interval. Establish whether the input contains trusted allocation intervals or raw events that still need pairing and deduplication. Then implement the calculation and test the boundaries that could change an invoice.

A strong answer

Suppose a tenant uses four GPUs for seven and a half minutes. At an illustrative rate of $1.10 per GPU-hour, proportional billing charges 4 × 1.10 × 0.125 = $0.55. Rounding the duration up to a whole hour charges $4.40. Both calculations can implement a policy; the programmer must not choose between them accidentally.

For this example, rates are decimal strings, durations are proportional, and each record is rounded to cents using half-up rounding. The invoice sums these rounded line items, subtracts a nonnegative credit, and floors the amount due at zero. Other contracts may round only the invoice total or retain finer precision in line items. Follow the agreed contract consistently.

from dataclasses import dataclass
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_UP

RATES = {"h100": Decimal("2.50"), "a100": Decimal("1.10"), "l4": Decimal("0.35")}
CENT = Decimal("0.01")

@dataclass(frozen=True)
class Usage:
    tenant: str
    gpu_type: str
    count: int
    start: datetime
    end: datetime

def hours(u):
    if any(t.tzinfo is None or t.utcoffset() is None for t in (u.start, u.end)):
        raise ValueError("timestamps must be timezone-aware")
    delta = u.end.astimezone(timezone.utc) - u.start.astimezone(timezone.utc)
    micros = (delta.days * 86400 + delta.seconds) * 1_000_000 + delta.microseconds
    if micros <= 0:
        raise ValueError("end must be after start")
    return Decimal(micros) / Decimal(3_600_000_000)

def cost(u):
    if type(u.count) is not int or u.count <= 0:
        raise ValueError("count must be a positive integer")
    if u.gpu_type not in RATES:
        raise ValueError("unknown GPU type")
    raw = RATES[u.gpu_type] * u.count * hours(u)
    return raw.quantize(CENT, rounding=ROUND_HALF_UP)

def bill(records, free_credit=Decimal("0")):
    if not isinstance(free_credit, Decimal) or not free_credit.is_finite() or free_credit < 0:
        raise ValueError("credit must be a finite nonnegative Decimal")
    totals = {}
    for u in records:
        totals[u.tenant] = totals.get(u.tenant, Decimal("0")) + cost(u)
    return {t: max(Decimal("0"), total - free_credit).quantize(CENT, rounding=ROUND_HALF_UP)
            for t, total in totals.items()}

def dt(s):
    return datetime.fromisoformat(s).replace(tzinfo=timezone.utc)

u = Usage("acme", "a100", 4, dt("2026-09-01T10:00:00"), dt("2026-09-01T10:07:30"))
assert cost(u) == Decimal("0.55")
assert bill([u], Decimal("50")) == {"acme": Decimal("0.00")}
month = Usage("acme", "h100", 1, dt("2026-08-31T23:00:00"), dt("2026-09-01T01:00:00"))
assert cost(month) == Decimal("5.00")
assert bill([u, month]) == {"acme": Decimal("5.55")}
for bad in [Usage("t", "unknown", 1, u.start, u.end),
            Usage("t", "a100", 0, u.start, u.end),
            Usage("t", "a100", 1, u.end, u.start)]:
    try:
        cost(bad)
    except ValueError:
        pass
    else:
        raise AssertionError("invalid usage accepted")

The duration calculation uses integer microseconds before converting to Decimal. Converting the float returned by total_seconds() into Decimal would preserve that float's approximation, not recover the original exact duration. Decimal division can still round to its configured precision; for a production calculator, choose and test the precision against the supported amount and duration ranges.

Binary floats do not represent every decimal fraction exactly. The size of an invoice error depends on the operations and rounding boundaries, so there is no universal record count at which floats lose a cent. Decimal makes the intended decimal arithmetic and rounding explicit. Integer subunits are another option when the chosen scale represents all intermediate charges; integer cents alone cannot exactly represent every prorated rate.

TestWhat it establishes
Seven-and-a-half-minute intervalPartial hours survive the calculation
Interval across midnightDuration remains correct across a date boundary
Zero, negative or boolean countInvalid quantities are rejected
Credit larger than chargesAmount due stops at zero
Half-cent boundaryThe agreed rounding rule is applied

The midnight test does not implement monthly allocation. This calculator prices an entire interval at one rate. If the contract splits usage across billing periods or rate changes, split the interval first and define how rounding applies to those pieces. Likewise, duplicated records must be handled before this loop using a stable usage identifier; matching timestamps alone do not prove two allocations are duplicates.

The GPU Credit Scheduler Pattern explains how reservation and usage events feed billing. If the input is an event log, first reconstruct valid intervals and resolve missing or repeated events. A short arithmetic function cannot repair ambiguous source data.

What interviewers probe next

  • "Why round per record?" It is this example's policy, so the displayed line items sum to the charge before credits.
  • "What happens when a rate changes?" Split the interval at the effective boundary and apply the versioned rates.
  • "Does rejecting a record mean crashing the entire service?" No. Surface an explicit error or quarantine it for reconciliation; never silently turn it into zero usage.
  • "What about duplicate input?" Require an allocation or usage identity and enforce deduplication before charging.

Common mistakes

  • Treating a rounding policy as an implementation detail.
  • Converting a float to Decimal after precision has already been lost.
  • Accepting negative GPU counts or mixing naive and aware timestamps.
  • Claiming a duration test also proves billing-period allocation.

Key takeaways

  • Four GPUs × $1.10/hour × 0.125 hours = $0.55 under proportional billing.
  • Decimal arithmetic still needs an explicit precision and rounding policy.
  • Validate the interval and quantity before calculating money.
You can read the first 10 answers per topic without an account. Signing in increases that to 20, includes lessons marked Plus, and saves your progress so you can return to topics you want to review.no card · Google sign-in · nothing to cancel
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 10 to 20 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

Explain the billing policy before the arithmetic. Check exact duration conversion, invalid counts, rounding boundaries and duplicate usage.

DISCUSSION · 0

No comments yet — be the first to share your approach.