AI Infra Interviews logo
Coding for Infra / 01
easy★ EssentialNewOpenAI

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

Three decisions decide whether this is correct: the numeric type, how partial hours are handled, and what happens to a record that does not make sense. The implementation with its tests, the floating-point trap that costs real money, and the questions to ask before writing any of it.

Updated Sep 2026 · Grounded in real AI infrastructure interview loops and written to a senior-engineer editorial bar, with every number worked and every diagram hand-built.

TL;DR: Ask three questions before writing anything: are partial hours billed proportionally or rounded up to the hour, what rounding rule applies to the final amount, and what should happen to a malformed record. Then the implementation is short. Use a decimal type rather than a float, because binary floating point cannot represent 0.1 exactly and repeated additions of cents accumulate error that eventually shows up as a mismatched invoice. Compute duration from timestamps in seconds and divide, so a 7.5-minute allocation of four A100s at $1.10 an hour comes to $0.55 rather than being rounded away. Validate each record: reject an unknown GPU type and reject an interval whose end is not after its start, both loudly, because a silent zero in a billing pipeline is worse than a crash. The tests that matter are the partial hour, the month boundary, the malformed record, and the free-credit floor, since a tenant whose usage is below their credit must owe zero rather than a negative amount.

How to approach it

Ask the three product questions first, because they change the code and an interviewer is checking whether you ask or assume. State the decimal decision with its reason. Write the small, boring, correct version. Then name the tests, since for a billing problem the tests are half the answer and the interesting ones are all edge cases.

A strong answer

A typical situation: a billing pipeline built with floats reconciles to within a few cents most months, and once a quarter a large tenant's invoice differs from the usage export by an amount somebody has to explain. The arithmetic was never wrong in a way anyone could point at, which is exactly the problem with accumulating floating-point error across millions of records.

The three questions:

1. partial hours: proportional, or rounded up to the whole hour?
   proportional is what most cloud billing does and what this implementation assumes
   rounding up is a real policy and it changes the code and roughly doubles small-allocation
   revenue, so it is a product decision rather than an implementation detail

2. rounding: to what precision, and which rule?
   cents, half-up, applied once at the end of each record's cost
   applying it per record rather than per tenant means the sum of line items equals the total,
   which is what a customer will check

3. malformed records: reject or skip?
   reject loudly. A billing pipeline that silently skips records under-bills, and nobody
   notices until an audit

The implementation:

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")}

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

def hours(u: Usage) -> Decimal:
    if u.end <= u.start:
        raise ValueError(f"end {u.end} not after start {u.start}")
    secs = Decimal((u.end - u.start).total_seconds())
    return secs / Decimal(3600)

def cost(u: Usage) -> Decimal:
    try:
        rate = RATES[u.gpu_type]
    except KeyError:
        raise ValueError(f"unknown gpu type {u.gpu_type!r}") from None
    raw = rate * Decimal(u.count) * hours(u)
    return raw.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)

def bill(records, free_credit=Decimal("0")):
    totals = {}
    for u in records:
        totals[u.tenant] = totals.get(u.tenant, Decimal("0")) + cost(u)
    return {t: max(Decimal("0"), v - free_credit).quantize(Decimal("0.01"))
            for t, v in totals.items()}

Running it on three records:

acme    h100 x8 for 6.5 h      -> $130.00
acme    a100 x4 for 0.125 h    -> $0.55
globex  l4  x1 for 696 h       -> $243.60
bill with $50 free credit: {'acme': '80.55', 'globex': '193.60'}

partial-hour check: 4 x $1.10 x 0.125 h = $0.55, which is the case a rounded-up
  implementation would have charged $4.40 for
unknown type rejected:     unknown gpu type 'h200'
reversed interval rejected: end 2026-09-01 00:00:00+00:00 not after start 2026-09-01 01:00:00+00:00

The decimal decision, which is the one an interviewer is listening for:

why not float
  binary floating point cannot represent 0.1 exactly, so 0.1 + 0.2 != 0.3
  a single record's error is far below a cent and invisible
  a million records summed accumulate error that can reach cents or more, and the direction
  is not predictable
  the failure mode: the invoice and the usage export disagree by an amount nobody can locate,
  which is the scenario above

why decimal
  exact decimal representation of the values money is actually denominated in
  explicit rounding at a chosen precision with a chosen rule, rather than whatever the
  hardware does
  slower, by a large factor, and entirely irrelevant here since this is not a hot loop

the general rule: money is decimal, and if performance ever makes that impossible, use
  integer cents rather than float
sanity: one record's float error is around 1e-16 relative, so a single invoice is fine and a
        year of 10 million records is not. The cost of decimal here is a few microseconds per
        record against a pipeline that runs once a month, so there is nothing to trade

The tests, which are the other half of the answer:

def test_partial_hour():
    u = Usage("t", "a100", 4, dt("2026-09-01T10:00:00"), dt("2026-09-01T10:07:30"))
    assert cost(u) == Decimal("0.55")          # 4 x 1.10 x 0.125

def test_month_boundary():
    # a record spanning midnight on the last day belongs to whichever period the
    # policy says; the function must not silently split or drop it
    u = Usage("t", "h100", 1, dt("2026-08-31T23:00:00"), dt("2026-09-01T01:00:00"))
    assert cost(u) == Decimal("5.00")

def test_unknown_type_raises():
    with pytest.raises(ValueError):
        cost(Usage("t", "h200", 1, dt("2026-09-01T00:00:00"), dt("2026-09-01T01:00:00")))

def test_reversed_interval_raises():
    with pytest.raises(ValueError):
        cost(Usage("t", "h100", 1, dt("2026-09-01T01:00:00"), dt("2026-09-01T00:00:00")))

def test_free_credit_floors_at_zero():
    u = Usage("t", "l4", 1, dt("2026-09-01T00:00:00"), dt("2026-09-01T01:00:00")))
    assert bill([u], free_credit=Decimal("50")) == {"t": Decimal("0.00")}

def test_line_items_sum_to_total():
    # rounding per record means the sum of what the customer sees equals what they pay
    assert sum(cost(u) for u in RECORDS) == sum(bill(RECORDS).values())

The month-boundary test deserves a note. A record spanning the boundary has to be handled by an explicit policy: attribute it to the period it started in, to the one it ended in, or split it. All three are defensible and the code must do one of them deliberately, because the case arrives every month and a function that has not decided will do something arbitrary.

The GPU Credit Scheduler Pattern covers the wider system this sits in, including where the usage records come from and how allocation events become intervals.

ONE ACCOUNT, ONE JOB, THREE TRANSITIONS available 100 initial balance 100 available 70 reserved 30 submit(30) balance 100 available 78 complete(22) balance 78 balance ≥ reserved ≥ 0 holds after every one of these, including a crash between any two. start() is the transition that changes nothing, which is the part candidates get wrong.

The reversal condition: this shape assumes usage arrives as clean, paired allocation intervals. Real event streams give start and stop events that can be unpaired, duplicated or out of order, and reconstructing intervals from them is the harder half of the problem. If the interviewer's input is an event log rather than intervals, the first work is pairing and validating, and Interval Merging and Utilization Logs is where that lives. Ask which you are being given before writing either.

What interviewers probe next

  • "What if a record is duplicated?" Deduplicate on an identifier before billing. Without one, exact duplicates are indistinguishable from a tenant running the same shape twice on purpose, which is a data-model problem rather than a code one.
  • "How would you handle a rate change mid-month?" Rates become time-ranged, and a record spanning a change splits at the boundary. That is why rates belong in a versioned table rather than in a constant.
  • "Why round per record?" So the line items a customer sees sum to the total they pay. Rounding once at the end produces a total that does not match the itemization.
  • "How do you handle time zones?" Store and compute in a single timezone, and convert only for display. Billing periods are defined in a stated zone and that definition belongs in the configuration.

Common mistakes

  • Floats for money, which accumulates error that surfaces as an unexplainable invoice discrepancy.
  • Silently skipping malformed records, which under-bills without any signal.
  • Rounding once at the end, so line items do not sum to the total.
  • Not asking whether partial hours are proportional, which changes both the code and the revenue.

Key takeaways

  • Ask three questions first: partial-hour policy, rounding rule, and what happens to a bad record.
  • Proportional billing of a 7.5-minute allocation of four A100s at $1.10 is $0.55; rounding up to the hour would charge $4.40 for the same usage.
  • Decimal for money, always; integer cents if decimal is somehow too slow, never float.
  • Validate and reject loudly: unknown types and reversed intervals raise rather than returning zero.
  • Test the partial hour, the month boundary, the malformed record, the free-credit floor, and that line items sum to the total.
That one was free — and so are 10 answers per topic without an account. Signing in doubles that to 20, opens the Plus lessons in the courses, and remembers which topics you keep getting wrong.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

The concepts behind this question

Ranked by how closely each one overlaps this question's topic, so the first card is the thing to read if the answer above moved too fast.

Foundational
🖧 Hardware & Cluster Build-Out
Burn-In and Acceptance TestingNew hardware fails early or it fails late, and burn-in exists to move the early failures before the cluster is handed over rather than after. A proper acceptance test runs every layer under sustained load for days, compares every node against its siblings rather than against a specification, and produces a signed number the buyer and the vendor both agree on. The comparison is the important part: identical hardware running identical work should produce identical numbers, and the outliers are the finding.
Advanced
💻 Coding for Infra🔒 Premium
Interval Merging and Utilization LogsGiven busy intervals per GPU, when was the whole cluster idle? What was the utilization per hour from a log of start and stop events? Which jobs overlapped? These are the interval problems of the infrastructure coding screen, and they share one tool: sort the endpoints and sweep. The sweep line turns every variant into a single pass with a counter, the sort is the only thing that costs more than linear time, and the edge cases (touching intervals, zero-length events, an unterminated start) are where candidates lose the round. This page works the standard problem and its relatives with code, tests and the complexity derivation.
Foundational
💻 Coding for Infra
The Practical Coding Screen PlaybookThe AI infrastructure coding screen is 45 to 60 minutes of building a small, realistic piece of systems code (a scheduler, a rate limiter, a batcher, a log parser, a cache) in the language you choose, with an interviewer who extends the problem twice and watches how you handle it. It is not a puzzle round: the score comes from working code early, tests that name the invariants, complexity said out loud, and calm follow-ups. Some companies allow an AI assistant and some ban it, and each policy changes what is measured. This page gives the minute-by-minute plan, the habits that score, and the mistakes that end the screen.
Foundational
🧮 Open Weights & Serving Engines
Model Onboarding: From Hugging Face to ProductionA new open-weights model lands and someone asks how long until it is serving traffic. The answer depends on a sequence that is the same every time: read the card and the config, check engine support for the exact attention and quantization combination, size it, pull the weights, bring up one replica, validate correctness against the authors' own outputs, benchmark, then roll out behind a flag. The steps that surprise people are the download, which is hours for a trillion-parameter model, and the correctness check, which almost nobody does and which catches the wrong template.
UP NEXT ON YOUR JOURNEY
FEDITOR'S NOTE

Scored on choosing decimal over float for money with the reason, on validating records rather than trusting them, and on asking the rounding and partial-hour questions before coding.

DISCUSSION · 0

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