Bilt Rewards lets renters earn points on rent and redeem them for travel, fitness, or a future home purchase. The source notes say a significant share of a Software Engineer's work is backend: point accrual and redemption services, transaction processing that has to stay accurate at high volume, and APIs for partner integrations. The notes also mention frontend components, and React or Next.js is listed as nice to have.
The reported questions track that work closely. Candidates describe practical tasks: loading a large transaction file into a database, correcting SQL errors in existing code while implementing new requirements, adding retry and timeout handling to API calls, and building an interface against a mock API. Algorithm questions still show up in the bank, including first unique character in a string, lowest common ancestor in a binary tree and a Hankel matrix check. Keep some algorithm practice, but spend most of your time on data loading, SQL correctness and failure handling.
The role requirements in the source notes list Java, SQL and database management, and RESTful API design as must-haves. Prepare as if Java fluency will be assumed: standard collections, clean class design, unit tests, and reading someone else's code fast enough to fix it. Two PracHub bank questions for this role show the shape of that work: debugging failing Java unit tests, and repairing and extending a transaction reward processor.
The notes also describe owning code from design through deployment, including unit tests, code review and production monitoring, and working with product managers and operations teams. Prepare behavioral answers about changing requirements and shifting priorities. Practise explaining technical choices in plain business terms, for example why a design never credits the same points twice.
Recruiter Screen
reportedCandidates describe this stage as a conversation with a recruiter about background and motivation. Prepare both before the call. The main risk is a generic answer about why you want the job. The product gives you specific material to connect your experience to: points earned on rent and redeemed for travel, fitness or a home purchase. Use the call to state practical constraints such as start date, location, work authorisation and compensation expectations. Also ask which format the technical assessment will take, because reports mention both take-home assignments and live coding.
What to demonstrate
- Whether your background summary names concrete impact, meaning what you built and what changed because of it, rather than a list of employers
- Whether your motivation connects to Bilt Rewards' rent-based rewards model and transaction-heavy backend work instead of general fintech enthusiasm
- Whether your constraints and expectations are clear enough that the rest of the process can be scheduled around them
How to prepare
- Prepare a spoken summary of your experience that leads with the two projects closest to transaction processing, data ingestion or API integration, each with one measurable result
- Prepare a why-Bilt answer that names the earn-and-redeem loop on rent and one engineering problem in it you want to work on, such as accurate point accrual at volume
- Ask the recruiter whether the technical assessment is a take-home or live coding, which language is expected, and whether development tools are allowed
Technical Assessments
reportedReports describe this stage as take-home assignments or live coding sessions. The sources do not say which questions appear here. The technical questions reported for the role are practical ones in three categories: file parsing and database loading, correcting SQL while adding requirements, and API error, timeout and retry handling. Practise each category in both formats. For a take-home, the source tip is to treat the submission like production code, with tests, documentation and graceful error handling. In live coding, talk through your reasoning and adapt when a requirement changes. The bank includes a live-coding-with-flexibility question that practises exactly that.
What to demonstrate
- Whether your Java is clean and structured, with small methods, sensible classes, and errors handled at the right layer rather than swallowed
- Whether file-loading code streams and batches its writes instead of reading everything into memory or inserting one row at a time, and what it does with malformed rows
- Whether you fix existing SQL precisely, explaining each error before adding the new requirement, and check the result against sample data
- Whether retry logic separates retryable failures (timeouts, 5xx, 429) from non-retryable ones, with backoff and a bounded attempt count
How to prepare
- Build a small Java program that streams a CSV of transactions, validates each row, writes valid rows in configurable batches, and records rejected rows with a reason
- Plant three errors in a SQL query (a join that multiplies rows, a missing GROUP BY column, an off-by-one date filter), fix them, add a new filter, and write the sample rows that prove each fix
- Write an HTTP client wrapper with explicit connect and read timeouts, exponential backoff with jitter, a maximum attempt count, and a stated rule for which status codes are retried
- For a take-home, submit unit tests, a README with assumptions and run instructions, and explicit handling of bad input
Onsite Interview
reportedCandidates describe the final round as deep dives into system design and behavioral competencies. The sources do not tie any reported question to this round, so prepare broadly. For design, practise the role's reported design category, which covers high-volume file processing with database updates, implementing a feature from a technical document, and building against a mock API, along with the bank's configurable reward points service and whiteboard system design questions. For the behavioral side, prepare stories about stakeholders and changing priorities. Be ready to explain the trade-off behind every choice and connect it to a business outcome, such as points that are never credited twice.
What to demonstrate
- Whether a high-volume ingestion design covers chunking, idempotent reprocessing, partial failure and progress tracking, not only the happy path
- Whether you raise data-integrity concerns for points and transactions (duplicates, reversals, reconciliation) without being prompted
- Whether you can turn a technical document into an implementation plan with open questions, sequencing, a test strategy and a rollout
- Whether your stakeholder stories show a real change in requirements, the trade-off you raised, and how you communicated it
How to prepare
- Work a millions-of-rows ingestion design end to end: upload, validation, chunked processing with checkpoints, idempotency per row or file, upserts, error reporting, and what a rerun does after a crash halfway through
- Sketch a reward points service with configurable earn rules, for example a points multiplier for one merchant category, and state whether a rule change applies to past or only future transactions
- Prepare two stakeholder stories about shifting priorities, each with the change, the trade-off you raised and the outcome
- Take a feature spec you have written or read and list the questions you would ask before writing any code
2 candidate reports. Individual accounts describe a particular role and hiring cycle.
Bilt Software Engineer Interview Experience — Codespace-Based, No LeetCode, AI-Assisted Design Task
A recruiter reached out and went straight to scheduling a technical interview. Their interview process was pretty confusing — they used a GitHub Codespace, zero LeetCode, and it was split into two main parts. Part one: fix failing unit tests. The project was structured similarly to a Spring MVC app, and it also tested SQL. I had to fix a few SQL queries and write the logic for determining reward…
Read full experienceBilt Rewards Software Engineer Interview Experience — Passed Every Unit Test, Still Rejected
A recruiter from this company reached out to me directly on LinkedIn — not a staffing agency. After the recruiter call, we scheduled a phone screen. Side rant: I actually have this credit card myself, and the Android app is painfully slow. They basically survive by hoodwinking Wells Fargo and their investors. Anyway, after replying on LinkedIn we set up an intro call, and this recruiter also emai…
Read full experiencePracHub editorial advice for the preparation topics above.
Loading a large transaction file one row at a time, with no plan for a rerun
Stream the file instead of reading it whole, validate each row, and write in batches inside transactions of bounded size. Then answer the question the interviewer is likely to ask next: what happens if the job dies halfway and runs again? Give each row a natural or derived key and write with an upsert, or record which batches finished, so a rerun cannot credit the same transaction twice. Send malformed rows to a reject list with a reason instead of failing the whole file.
Retrying every failed API call the same way
Sort failures before you retry. Timeouts, connection errors, 5xx and 429 are candidates for retry. Validation errors in the 400 range are not. Use exponential backoff with jitter and a maximum attempt count, and set connect and read timeouts explicitly. Say what makes a retry safe: a POST that moves points or money needs an idempotency key, or a retry after a lost response becomes a duplicate. Also say what the caller sees when retries run out.
Rewriting the SQL from scratch instead of fixing the errors you were given
When you get existing SQL with errors plus a new requirement, fix the errors first, one at a time. For each, say what it produced wrong, for example a join that multiplies rows, a filter that silently drops NULLs, or an aggregate missing a GROUP BY column. Only then add the requirement. Check the result against a few sample rows whose expected output you worked out by hand. A full rewrite hides whether you understood the original bug.
Submitting a take-home that works only on the happy path
Treat the take-home as production code, as the source notes advise. Include unit tests for normal and edge input, a README with assumptions and run instructions, clear handling of malformed input and failed calls, and readable Java classes. Keep the scope tight, and list what you would add with more time rather than leaving features half-built.
Giving a generic answer to why Bilt Rewards
Connect your answer to the rent-based rewards model: points earned on rent and redeemed for travel, fitness or a home purchase, plus the transaction processing and partner integrations behind it. Name the part of that work your experience fits, such as ledger accuracy, data ingestion or API integration, and one problem you would want to own.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Find overlapping job attempts and peak concurrency from lease records
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
- State the clock caveat: started_at and finished_at are written by different workers, so under skew of a few hundred milliseconds an apparent overlap shorter than that bound is not evidence. Filter reported overlaps by a minimum duration, or prefer timestamps written by whichever component heartbeats the lease.
- Scale the sort rather than assuming it fits: the sweep emits two endpoints per attempt, so 50,000,000 records become 100,000,000 endpoints, and at roughly 24 bytes each, an 8-byte timestamp plus a 4-byte job_type plus a kind flag padded to alignment, that is about 2.4 GB of sort keys before any scratch space. Either push the ordering into the database behind an index on (job_type, started_at) or run an external merge sort in chunks; the overlap pass sorts n records rather than 2n, so it is the cheaper of the two.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
- How would you compute both answers incrementally as records arrive rather than in a daily batch?
Canonicalise a request body into a stable idempotency fingerprint
idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.
Approach
- Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
- Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
- Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
- Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.
- Frame the hash preimage so concatenation cannot collide: delimit or length-prefix the method, path and body, otherwise one request's fields can be rearranged into another request with the same byte stream and the same fingerprint.
- Name the refusals and their consequence: no case folding, no dropping of null-valued keys, no Unicode normalisation. Each makes two different requests fingerprint alike, and the resulting failure is the worst one this table has, since the second request is answered with the first one's stored response and its effect never happens.
Worked solution 25 min
- Write the serialiser: recursive emit with a depth counter, objects sorted by UTF-8 key bytes, arrays in order, strings escaped by one fixed rule, numbers emitted as their original token.
- Run it over three bodies: the same object with keys reordered, the same object with \u0041 written as A, and one with a nested array reversed. The first two must produce identical bytes and the third must not.
- Take the id 9007199254740993, round-trip it through a double, show it returns as 9007199254740992, then state the rule that prevents this.
- Define the hash preimage explicitly with its delimiters, and construct a pair of (path, body) inputs that would collide without them.
Follow-up
- A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
- Where does the fingerprint get computed relative to request decompression and the body-size limit?
- The endpoint takes 1,000 requests per second with 256 KB bodies. What does hashing cost, and does it belong at the edge or in the core service?
Track a rolling failure rate per destination for circuit decisions
The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.
Approach
- Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
- Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
- State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
- Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.
- Expire idle destinations, or memory grows with every destination ever seen rather than with the live set. Hold the rings in a bounded LRU keyed on destination_id and treat a miss as no history, which is the correct default for an endpoint that has been silent for a minute.
- Keep the half-open probe out of the window arithmetic. After the circuit opens, one probe per interval decides whether to close it, and folding that single success into a window that still holds a 100 percent failure history would reopen the destination on one data point.
Follow-up
- The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
- A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?
- How would you make the window survive a process restart, and is it worth the cost?
Hold a per-tenant active cap against concurrent creates
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
- For the job case the invariant is expressible per row, so let the database hold it: a partial unique index on job_run (tenant_id, job_type) WHERE status IN ('queued','running') makes a second running export unwritable and the loser takes 23505, mapped to 409. That is strictly better than a counter — no drift, no reconciliation — and it is available only because the cap is one rather than fifty.
- Add the retry discipline each route demands: under SERIALIZABLE both 40001 and deadlock 40P01 are retryable and the retry must re-execute the read, while under READ COMMITTED with the counter nothing retries, because the conflict is reported to the caller rather than raised as an error.
Worked solution 35 min
- Reproduce with two sessions that both count 49, both insert and both commit, at READ COMMITTED and then at REPEATABLE READ; record the final active count for each.
- Repeat both sessions at SERIALIZABLE and record which SQLSTATE the loser receives and at which statement it is raised.
- Implement the counter form and run a 20-way concurrent create against a tenant sitting at 45 active resources.
- Implement the partial unique index for the job case and race 20 enqueues of the same export.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
- How do you detect after the fact that the counter drifted, without locking the table?
Find version gaps and relay lag with window functions
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
- Interpret rather than report: no gaps plus a normal p95 of published_at - created_at points at the consumer; gaps or a fat lag tail point at the relay; rows still 'pending' with attempts > 0 point at neither, because they never left the database.
- Be explicit that the partial index on (created_at, event_id) WHERE status = 'pending' does not serve any of these — they read published rows. Name the index a recurring monitor would need, and say why a query run twice a year may not deserve one.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
- The consumer claims it never received event 4,812,006. What do you look at, in what order?
How do you approach the implementation of a new feature when given a t…
How do you approach the implementation of a new feature when given a technical document?
Approach
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
What considerations do you make when building an interface that intera…
What considerations do you make when building an interface that interacts with a mock API?
Approach
- Name the failure you are designing for, then the recovery path.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
Follow-up
- What would you drop to keep the system up under load?
- What breaks first when traffic grows ten times?
How would you handle errors and timeouts when making API calls, includ…
How would you handle errors and timeouts when making API calls, including implementing a retry mechanism?
Approach
- State your assumptions explicitly before working the problem.
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Keep one unresponsive destination from stalling all webhook delivery
Egress delivery sends about 1.5k webhooks/second to 40k destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. One destination begins accepting connections and never responding; within the hour 150 destinations behave the same way. Design the delivery path so unrelated destinations are unaffected: the pool structure, the timeouts, the retry policy, the per-destination circuit, and what is recorded so a retry is not a second effect at the receiver. State how many in-flight slots the degraded destinations hold and why that number decides the design.
Approach
- Start with the number, and with the law that produces it. In-flight work is arrival rate times time in service, so 1.5k/second against a healthy 200 ms response needs about 300 concurrent slots. Per destination the same product applies, ceilinged by the concurrency cap: at the fleet average of 0.0375 deliveries/second per destination (1.5k spread over 40k) a 10-second timeout is 0.375 slots. A destination that has queued retries behind it is a different regime - every slot refills the instant an attempt expires, so it sits pinned at its cap of 4 - and 150 of those hold 600 slots, more than a pool sized for healthy traffic, entirely consumed by endpoints that will never answer. The per-destination cap bounds one endpoint and says nothing about the aggregate, which is exactly why it alone is not containment.
- Contain with bulkheads and an admission bound rather than a larger pool. Cap total in-flight per pool and shard destinations across pools by a hash of destination id, so a correlated group - one provider, one region - cannot exceed its pool's share. A delivery refused admission and re-queued with backoff is strictly better than one holding a slot on behalf of a receiver that is not listening.
- Treat the timeout as two timeouts, and be exact about what shortening one buys. Connect and read are separate failures and both must be shorter than the budget of whatever is waiting. Occupancy is min(cap, arrival rate x timeout), so dropping the read ceiling from 10 seconds to 3 cuts a merely slow destination's occupancy proportionally, 0.375 slots to 0.11 at the fleet average. It does not cut the 4 slots held by one of the 150: a destination with a retry backlog arrives far above cap/timeout - 0.4/second at a 10-second timeout, 1.33/second at 3 - so it stays pinned at the cap either way and only the slot-seconds per attempt fall. What that does buy is detection rate: 3.3x more failures observed per second on the same four slots, which is how fast the circuit reaches its threshold. Pick the value from the measured latency distribution of successful deliveries, with their high percentile as the floor, not from a round number.
- Add a circuit per destination, counting a timeout as a failure. Once open, fail fast without taking a slot - that is the whole point, converting 4 held slots into zero. Half-open on a schedule with exactly one probe and close only if the probe succeeds, so a permanently dead endpoint costs one request per interval instead of a growing retry queue.
- Make retries safe and non-synchronising. Back off with full jitter, sleeping a random value in [0, min(cap, base x 2^attempt)], because a fixed delay re-synchronises every failed delivery to one destination into a simultaneous burst. Delivery is at-least-once, so the payload carries the event id under the signature and the receiver deduplicates on it; record the attempt against (destination, event id) rather than a bare success flag, so a lost response does not become a second business effect on the other side.
Worked solution 25 min
- Compute healthy in-flight from rate times latency, then slots held by 150 destinations at the cap and the full timeout, and compare both against one pool size.
- Write the pool sharding rule and the admission bound, and state what a refused delivery does next.
- Pick connect and read timeouts from the success-latency distribution, then compute min(cap, arrival rate x timeout) for an average destination and for one with a retry backlog, and say which of the two the shorter timeout actually moves.
- Write the circuit's state machine with its open threshold, probe interval and close condition, and the backoff formula with full jitter.
Follow-up
- The destination is not dead - it answers in 9.5 seconds with a 200. Does a failure-rate circuit open? Should anything shed that traffic, and on what signal?
- One destination requires deliveries in order. What does a per-destination concurrency of 4 do to that guarantee, and what would you change to offer it?
- A destination has been parked six hours with 900k undelivered events. What does resuming look like, and is delivering the whole backlog the right call?
Listing latency scales with page size, not with filters
The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.
Approach
- Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
- Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
- Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
- Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.
- Keep the tenant predicate in the batched query. The per-row version was implicitly scoped because its ids came from tenant-scoped rows; a batched user_id = ANY(...) with no tenant_id is an unscoped read that behaves correctly only as long as the id list is trustworthy.
- Re-measure at 10, 50 and 200 rows and confirm the statement count is constant. Latency should now track bytes returned.
Follow-up
- The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
- How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?
- The latest-revision actor is only used to render an avatar. Make the case for denormalising it onto resource, and name the write anomaly that introduces.
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Java fundamentals and reading unfamiliar code
- Review Java collections, equals and hashCode, immutability and exception handling, then write two small classes with unit tests to practise the OOP Classes in Practice question type
- Take a small Java project with failing unit tests, make them pass without editing the tests, and write one line per fix naming the root cause, as preparation for the bank question on debugging failing Java unit tests
- Solve First Unique Character Index and Lowest Common Ancestor in Java, stating the approach and complexity before writing code
Deliverable: Two tested Java classes, a list of test fixes with root causes, and two solved algorithm problems with stated complexity.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Parse and load a large transaction file
- Write a Java loader that streams a transactions CSV, validates each row, and inserts valid rows in configurable batches inside bounded transactions
- Send malformed rows to a reject file with a reason, then kill the job halfway and rerun it, confirming that no transaction is loaded twice
- Compare row-by-row inserts with batched inserts on a large generated file and write down where the time went
Deliverable: A working loader that is safe to rerun, plus a short note on batch size, reject handling and rerun behaviour.
Practice prompt ↗Practice prompt ↗03SQL correctness under a changing requirement
- Plant three errors in a merchant-and-transactions query, fix them one at a time with a sentence on what each produced wrong, then add a new requirement such as tripling points for one merchant category
- Work the worked exercise on holding a per-tenant active cap against concurrent creates, and relate its write-skew lesson to crediting points twice
- Write the queries for the practice question on finding version gaps and relay lag with window functions, and say where each window is evaluated relative to WHERE and LIMIT
Deliverable: A corrected and extended query with sample rows proving each fix, plus the completed worked exercise on the per-tenant active cap.
Practice prompt ↗Practice prompt ↗04API errors, timeouts, retries and mock APIs
- Build an HTTP client wrapper with connect and read timeouts, exponential backoff with jitter, a bounded attempt count and an explicit list of retryable status codes
- Test the wrapper against a mock API that returns timeouts, 500s, 429s and 400s, and list the interface considerations the mock exposed, such as loading and error states in a UI
- Work the worked exercise on canonicalising a request body into a stable idempotency fingerprint, and state when a retried POST is safe
Deliverable: A tested retry wrapper, a list of mock-API interface considerations, and the completed idempotency fingerprint exercise.
Practice prompt ↗Practice prompt ↗Worked solution ↗05System design: millions of rows and a reward points service
- Design the reported file-processing system end to end: upload, validation, chunking with checkpoints, idempotent upserts, error reporting and recovery after a crash
- Sketch a configurable reward points service: earn rules, how a rule change applies to past and future transactions, and how accruals are reconciled
- Work the worked exercise on keeping one unresponsive destination from stalling all webhook delivery, and reuse its timeout and circuit ideas for partner API calls
Deliverable: Two design sketches, each with its data model, failure handling and the trade-offs you would defend, plus the completed webhook delivery exercise.
Practice prompt ↗Practice prompt ↗06Recruiter screen and behavioral answers
- Prepare a spoken summary of your experience that leads with projects closest to transaction processing, data ingestion or API integration
- Prepare a why-Bilt answer tied to the rent-points-redemption model and one problem you would want to own
- Prepare two stakeholder stories about requirements or priorities changing, each with the trade-off you raised and the outcome, plus a strengths-and-weaknesses answer with a real weakness and what you do about it
Deliverable: Written notes for four behavioral answers, each rehearsed aloud at least once.
Practice prompt ↗Practice prompt ↗07Full rehearsal: repair and extend a transaction reward processor
- Take a small Java reward processor with a bug and a missing feature, fix the bug, add the feature, and narrate your reasoning aloud as you would in live coding
- Package the same work as a take-home: unit tests, README with assumptions, error handling for bad input
- Review the week: redo the task you were slowest on and note which mistakes came from the approach and which from Java syntax
Deliverable: A repaired and extended processor submitted in take-home form, plus a short list of the mistakes to watch for in the real session.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The behavioral prompts reported for this role cover three things: your career narrative and impact, why Bilt Rewards, and how you handle stakeholders when requirements or priorities change. For the stakeholder prompt, pick a case where the change was real and costly, and say what you traded off, how you told people, and how it turned out. For motivation, connect your experience to transaction processing, data loading or partner APIs rather than to fintech in general.
What are you looking for in your next role, and why are you interested…
What are you looking for in your next role, and why are you interested in Bilt Rewards?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
How do you deal with stakeholders when requirements change or prioriti…
How do you deal with stakeholders when requirements change or priorities shift?
Approach
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Tell me about your professional experience and the specific impact you…
Tell me about your professional experience and the specific impact you have had in previous roles.
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
- 01
Tell me about your professional experience and the specific impact you have had in previous roles.
- 02
What are you looking for in your next role, and why are you interested in Bilt Rewards?
- 03
How do you deal with stakeholders when requirements change or priorities shift?
- 04
What are your strengths and weaknesses?
Is this an official Bilt Rewards interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Bilt Rewards. Rounds and questions reflect what candidates have reported, not a process Bilt Rewards has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How much time should I dedicate to preparation?
The source notes suggest at least two to three weeks of focused practice, given the mix of practical coding and system design. If you have less time, follow the seven-day plan on this page. Weight it toward file loading, SQL fixes and API retries, which cover most of the reported technical questions.
PracHub interview research ↗Are AI tools allowed during technical interviews?
Some candidates report being allowed standard development tools, including AI, for reference. Ask your recruiter before the session. Either way, be ready to explain every line you submit, because the point of the exercise is your own understanding.
PracHub interview research ↗What is the best way to stand out?
Connect technical choices to their product effect. When you design a loader or a retry policy, say what it protects: points that are never credited twice, a member who is not charged or rewarded incorrectly, a partner integration that recovers without manual cleanup. Do this alongside a correct solution, not instead of one.
PracHub interview research ↗What is the typical timeline for the process?
Candidates report three rounds over roughly three to five weeks, and several weeks between the recruiter screen and a decision is not unusual. Keep in regular contact with your recruiter, and mention any competing deadline early.
PracHub interview research ↗Which language should I prepare in?
The source notes name Java and SQL for this role. Prepare your coding in Java unless the recruiter tells you otherwise, and be comfortable writing and correcting SQL by hand. React or Next.js is listed only as nice to have.
PracHub Software Engineer practice ↗Should I still practise algorithm problems?
Yes, but not as your main focus. Most reported technical questions are practical, such as file parsing, SQL fixes and API retries. The bank also includes first unique character in a string, lowest common ancestor in a binary tree and a Hankel matrix check. Cover arrays, strings, hash maps and trees well enough to state an approach and its complexity before you code.
PracHub Software Engineer practice ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24