cswork / libraryOpen practice workspace

Interview guides · Braze

Braze Software Engineer Interview Guide

Braze Software Engineer interview guide: progressive coding, a live debugging round with failing tests, system design with SQL, and a 7-day prep plan.

The 60-second brief

The Software Engineer role at Braze covers real-time customer engagement: personalized notifications, in-app messaging surfaces such as Content Cards, cross-channel campaigns, and real-time streaming data pipelines. For interview prep, the relevant point is that the reported design questions come from the same areas: event ingestion, message delivery guarantees, campaign data modeling and reporting.

This guide covers the Braze Software Engineer loop as candidates report it: a recruiter call, a technical screen with multi-part algorithmic problems, a virtual onsite with a system design module, a code debugging session on an existing repository, and a behavioral interview with an engineering manager. It sorts the reported questions by category (coding, debugging, design with SQL, behavioral), explains what to prepare for each round, and gives a seven-day plan linked to the practice questions and worked exercises on this page.

Focus areas

  • Scope every query and cache key by tenant
  • Keep money in integer minor units
  • Make every write idempotent under client retries

Overview

Software Engineers at Braze work on the systems behind personalized notifications, in-app messaging surfaces such as Content Cards, cross-channel campaigns and real-time streaming data pipelines. This guide's source notes list channels infrastructure, cloud data ingestion and customer-facing dashboard interfaces as areas an engineer might join. The technologies they list are TypeScript, React, Node.js, Python and PostgreSQL, with Vue.js named for frontend work.

The problem areas listed for the role are asynchronous event orchestration, state management, real-time targeting and message delivery. The reported interview questions cover the same ground. Design prompts include a notification delivery engine with at-least-once delivery, a webhook ingestion pipeline, and a campaign manager schema with SQL for cohort targeting. Coding prompts include grouping and sorting a stream of user activity events against rule definitions.

The format is what stands out in candidate reports. Besides algorithm and design rounds, candidates describe a debugging session: you get a repository with failing unit tests and unclear legacy code, and you make the tests pass without changing the public interface. Blank-page coding practice does not exercise this skill, so practise it separately: budget real time for reading and repairing unfamiliar code from a terminal.

The Braze interview process

Braze candidates report 5 rounds · ≈ 4-6 weeks. The stages below are what candidates describe, not a published process.

Recruiter Call

Candidates describe this as a conversation about your background, project experience and salary expectations. It is not a technical round, but it shapes the rest of the loop. Give a short walkthrough of recent projects that someone outside engineering can repeat accurately, say which kind of work you want (backend services, data ingestion, frontend dashboards), and have a considered answer on compensation. Candidates report the technical screen can be either live or a proctored assessment, so use this call to ask which one you will get, on what platform, and in which languages.

What to demonstrate

  • Whether a non-engineer can repeat your project summary without getting it wrong. Name each system by what it did, not by an internal codename
  • Whether you answer on salary expectations with a considered position, since candidates report the topic comes up on this call
  • Whether your background walkthrough stays short and in order, leaving time for the recruiter's questions about your project experience

How to prepare

  • Write each headline project in two sentences: what was breaking, what you changed, what happened after. Remove acronyms and codenames, then say it aloud to someone outside engineering
  • Decide your compensation position before the call, including how you will answer if asked for a number first
  • Prepare three questions about the next step: live or proctored screen, which platform, and whether you can choose your language

Technical Screen

Candidates report either a live technical screen or a proctored assessment, on a platform such as HackerRank or a live collaborative editor. The problems are algorithmic and build across sub-problems. A reported progressive coding example (round not specified): validate nested brackets, then measure maximum nesting depth, then apply custom rules. The main risk in this format is solving part one in a way that cannot take part two's rule without a rewrite. Reported coding questions from the loop (round not specified) also include grid traversal with obstacles, grouping and sorting event records by rule definitions, grid-based matching logic, and an in-memory structure for time-series threshold checks, so they are useful practice for this screen.

What to demonstrate

  • Whether part one is correct and tested quickly enough to leave room for the later parts
  • Whether the part-one structure extends to later rules. For example, a stack that can also track depth, not a counter that only checks balance
  • Whether you state time and space complexity for each part and notice when a new constraint changes it
  • Whether you ask about input shape and edge cases (empty input, unmatched closers, grid bounds) before writing code

How to prepare

  • Build a three-part ladder for bracket validation (valid or not, maximum depth, then custom rules) and solve it in one sitting without rewriting part one. For the rules step, a practice extension of your own is custom bracket pairs from a lookup table
  • Practise Find Valid Parentheses in String, Grid BFS and Sorting, 2D Board Replace Repeats and Longest Substring Without Repeats from the bank, adding one follow-up rule of your own to each
  • Practise in a plain browser editor and in a timed assessment tool, since candidates report both formats
  • Before submitting each part, run it against a small input you wrote yourself, including the empty case

Virtual Onsite Loop

Candidates describe the virtual onsite as several modules: a live debugging and refactoring session, a distributed system design module, and a behavioral interview with an engineering manager. It is sometimes split over two days. The debugging and behavioral modules have their own steps below, so this step covers design. One reported design scenario is a processing engine for a web scraping and ad analytics service, including the relational schema and explicit SQL for user reporting. Reported design questions from the loop (round not specified) also include a notification delivery engine with at-least-once delivery across mobile and web channels, a campaign manager schema with cohort-targeting SQL, and a pipeline that turns webhook payloads into structured records. Candidate reports also say not to skip database schema design in this module.

What to demonstrate

  • Whether you produce a concrete relational schema (tables, keys, indexes) and write the SQL the design needs, not just draw boxes
  • Whether you separate ingestion from processing with a queue, and explain what happens when a consumer fails partway through
  • Whether at-least-once delivery comes with deduplication or idempotency keys, so a retry does not send a user the same notification twice
  • Whether you ask about scope and traffic before choosing caches, replicas or partitioning

How to prepare

  • Design the campaign manager schema (campaigns, users, segments, sends) and write two cohort-targeting queries against it, then check which index each query needs
  • Walk through the notification engine's failure cases in order: provider timeout, worker crash after send and before acknowledgement, duplicate enqueue. Name the idempotency key that absorbs each one
  • Sketch the ad scraping and analytics pipeline from the bank questions end to end: fetch scheduling, storage, aggregation, and the query behind one dashboard
  • Work through the lease-and-fencing worked exercise on this page to practise the retry and exactly-one-outcome reasoning a delivery engine needs

Code Debugging Session

Candidates describe getting a repository of existing code with failing unit tests. Reported examples include a text encryption or cipher implementation, a data parser, and a legacy utility module with unclear variable names. You set up the environment, run the tests, isolate the logic errors and make the tests pass without changing the external interface. Some reports add a refactor step once the bugs are fixed: split a monolithic processing method into testable functions. Less common reported variants involve asynchronous race conditions in state management that cause intermittent test failures. Do not rewrite the code. Run one failing test at a time, trace it to the line that is wrong, fix that line, and re-run the full suite.

What to demonstrate

  • How quickly you get the project running and run a single test from the terminal instead of the whole suite
  • Whether you name a hypothesis for each failure and check it with a print, breakpoint or narrower test before editing
  • Whether your fixes keep the public API and the tests that already pass unchanged
  • Whether you finish fixing before you refactor, and keep the refactor small enough to verify

How to prepare

  • Take a small open-source library, plant three bugs (an off-by-one, a wrong boundary in string handling, a mishandled edge case), then fix them while narrating, running only the failing test each time
  • Learn the single-test commands for the frameworks you would use, such as pytest -k and a Jest name filter, until you can type them without looking them up
  • Practise Find Bugs in Python Code from the bank, and write a one-line root cause for each bug before fixing it
  • For the async variant, reproduce a race in a small script (two awaits updating shared state), make it fail every time, then fix it

Behavioral Interview

Candidates describe this as a conversation with an engineering manager about past experience and fit. Reported prompts include: a trade-off between speed of delivery and long-term maintainability; working with ambiguous requirements alongside product teams; a production incident you diagnosed, resolved and prevented from recurring; critical feedback in code review and how you changed your implementation; and a disagreement with a senior engineer's architectural decision. Each prompt needs a specific story with a decision point, what you knew at that moment, and what changed afterwards.

What to demonstrate

  • Whether the incident story covers diagnosis, resolution and prevention, not just the fix
  • Whether the trade-off story names both options and what you gave up, not only the option you chose
  • Whether you describe how you pinned down ambiguous requirements with product partners before building
  • Whether you take code review feedback on the merits and can say what changed in the code

How to prepare

  • Prepare one story each for the reported prompts, including a disagreement with a senior engineer, and give each a single decision point
  • For the incident story, write down the symptom, the first wrong hypothesis, the evidence that ruled it out, the fix, and the control you added afterwards
  • Rehearse each story with someone who interrupts with follow-up questions, and cut any story that only works when told without interruption

Where candidates lose points

Rewriting the legacy module in the debugging session instead of fixing the failing tests

The reported task is to make failing tests pass without changing the external interface. Run one failing test, read its assertion, trace it to the line that is wrong, and fix that line. Re-run the full suite after each fix to catch regressions. Refactor only after every test passes, and only as far as you can verify with the existing tests.

Editing code in the debugging session without saying what you expect to find

Say each hypothesis before you touch anything, for example: 'I think the boundary check skips the last character; if so, this input fails and this one passes.' Then check it. When the interviewer is quiet, keep narrating what you are checking and why, and ask for feedback directly instead of guessing how it is going.

Solving part one of a multi-part coding problem in a way that cannot absorb part two

Before coding part one, ask whether more parts will follow, and pick a structure with room to grow. A stack handles bracket validation, nesting depth and later rule changes such as custom pairs from a lookup table. A single counter handles only the first. Test part one on your own inputs, then extend it without rewriting.

Skipping the database schema and SQL in the design module

Candidate reports say this module expects a relational schema and explicit queries, such as cohort targeting for a campaign manager or reporting for an ad analytics pipeline. Write the tables with keys and indexes early, then write at least one real query and name the index it uses. Add queues, caches and partitioning only after the data model holds up.

Promising at-least-once notification delivery without explaining how duplicates are handled

At-least-once delivery means retries, and retries mean duplicates. Name the idempotency or dedup key (for example, campaign, user and channel), say where it is checked, and walk through a worker crash after the provider accepted the send but before the acknowledgement. Say what the user sees in that case.

Questions & practice

Implement a function to evaluate string pattern matching and handle complex nested parentheses validation with depth constraints.

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. Walk one small example through your approach before writing the whole thing.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Solve a progressive matrix traversal problem, handling dynamic obstacles and scaling the solution across higher dimensions.

Medium · data structures and algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. Name the brute-force solution and its complexity before improving on it.

Follow-up

  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Given a stream of incoming user activity events, group and sort the event records based on dynamic pipeline rule definitions.

Medium · data structures and algorithms

Approach

  1. Name the brute-force solution and its complexity before improving on it.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. Walk one small example through your approach before writing the whole thing.

Follow-up

  • How does this change if the input no longer fits in memory?
  • Which test case would catch an off-by-one here?

Parse and verify a timestamped multi-signature webhook header

Easy · parsing · hmac · constant-time-compare · replay-protection

An inbound webhook carries a signature header of at most 1 KiB shaped t=<unix seconds>,v1=<64 hex chars>, with up to five v1 values during secret rotation and possibly unknown scheme keys. You hold the raw request body bytes and the currently active signing secrets. Write the parser and the verifier: accept when any active secret reproduces a signature and the timestamp is within a five-minute tolerance in either direction, reject otherwise. Single left-to-right pass over the header, no regular expression. State what is inside the MAC and why.

Approach

  1. Parse in one scan: split on ,, then on the first = only, since a value may itself contain = under a future scheme. Accept t exactly once and treat a second t as a reject rather than last-wins. Push every v1 onto a short list and ignore any other key, so a v2 can be introduced later without breaking this verifier.
  2. Say what is signed: HMAC-SHA256 over the exact byte string <t>.<raw body bytes>, yielding 32 bytes or 64 hex characters. The timestamp sits inside the MAC because otherwise an attacker replays yesterday's body with its still-valid signature and only has to edit the header timestamp.
  3. Hash the bytes as received. Verifying against a re-serialised JSON body is the usual defect: key order, whitespace and number formatting all change the bytes while the parsed objects compare equal, so signatures fail for honest senders and the popular 'fix' is to stop checking.
  4. Compare in constant time over fixed-length digests. Decode the hex to 32 bytes, accumulate acc |= a[i] ^ b[i] across the whole length, and test acc == 0 at the end. Evaluate every candidate without an early exit; at five candidates that is five HMACs over the body, linear in body size and negligible beside the network.
  5. Apply the tolerance as a two-sided bound, rejecting when |now - t| > 300 seconds. A sender whose clock runs ahead of yours is an ordinary case, and an unbounded future timestamp is a free replay window.
  6. Complexity: O(L) over the header producing k candidates, plus k HMACs at O(|body|) each. Space is O(k) beyond the body itself. Do the cheap rejections, including the tolerance check, before any cryptography runs.

Worked solution (15 min)

  1. Write the grammar on one line before coding: header := field (',' field)*, field := key '=' value, split on the first = only.
  2. Implement the parser to return {t: int, v1: [hex, ...]}, rejecting a missing t, a duplicate t, any v1 that is not 64 hex characters, and a header over 1 KiB, all before any cryptography runs.
  3. Implement the verifier: for each active secret compute HMAC-SHA256(secret, f'{t}.'.encode() + raw_body), compare it in constant time against each parsed v1, and OR the results with no early exit.
  4. Test with a valid signature; the same body with t moved 400 seconds into the past; the same body with t 400 seconds into the future; a header carrying an unknown v2= alongside a valid v1; and a body re-serialised with different JSON key order.

Expected result: The valid case accepts. Both out-of-tolerance cases reject, including the future one. The unknown v2 field is ignored and the v1 still verifies. The re-serialised body fails, which is correct and is exactly why the raw bytes must be retained.

Verify it yourself

  • Timing the comparison over 10,000 mismatching signatures that differ at the first character versus the last shows no separation you can measure.
  • Removing t from the MAC input makes an old captured body with its original signature pass. Write that test, watch it pass, then put t back.
  • Every rejection except the signature mismatch itself returns before an HMAC is computed.

Follow-up

  • The body is 40 MB. What changes about where you verify, and what can you do before the whole body has arrived?
  • A customer reports that signatures fail for exactly the requests whose body contains a non-ASCII character. What is your first hypothesis?
  • How do you rotate the signing secret with no failed deliveries, and how long do both secrets stay live?

Migrate a live partitioned event table without blocking ingest

Hard · online migration · ddl locks · expand and contract · partitioning

usage_event is range-partitioned daily on ingested_at, holds roughly 250M rows per day across 400 live partitions, and is written at 10-40k rows/second. Two changes are required: quantity must move from double precision to numeric(20,6), and a new environment column must become NOT NULL with a default of 'production'. Ingest cannot stop. Give the ordered plan, naming for each step the lock it takes, what that lock blocks, and roughly how long it is held. Identify the one step that cannot be rolled back cleanly once traffic depends on it.

Approach

  1. Classify the two changes before planning anything. Adding a column with a non-volatile default has been metadata-only since PostgreSQL 11, so it is cheap. Changing double precision to numeric is not binary-coercible, so alter column ... type rewrites every partition under ACCESS EXCLUSIVE and rebuilds its indexes; on this volume that is hours of blocked ingest and is simply not an option, which is why the plan is expand-and-contract rather than one statement.
  2. Expand: add quantity_numeric numeric(20,6) and environment with its default on the parent. Both are catalogue-only but both take a brief ACCESS EXCLUSIVE that cascades to partitions, so run each with lock_timeout set to a second or two and retry on failure. A queued ACCESS EXCLUSIVE request blocks every reader behind it, which is how a metadata-only change turns into an outage.
  3. Dual-write: deploy producer code that populates both columns on every insert, and leave it running before anything reads the new column. This is the step that cannot be reverted cleanly. Once readers depend on quantity_numeric, reverting the writer leaves rows with a null there, and the gap is only discoverable by re-reading the old column, which the readers have stopped doing.
  4. Backfill older partitions in batches keyed on the primary key, oldest first, committing every few thousand rows with a pause between batches, and skipping the partition still receiving writes until it rotates. Each batch is an ordinary UPDATE taking row locks only. The cost is bloat and WAL rather than blocking, so watch dead tuples and let autovacuum keep pace instead of wrapping 400 partitions in one transaction.
  5. Make NOT NULL cheap with the three-step form: add constraint ... check (environment is not null) not valid (brief ACCESS EXCLUSIVE, no scan), then validate constraint (SHARE UPDATE EXCLUSIVE, scans while reads and writes continue), then set not null, which from PostgreSQL 12 uses the validated check and skips its own full scan. Do this per partition, then on the parent.
  6. Switch and contract: move reads to the new column behind a flag, verify over a full period that both columns agree on freshly written rows, drop the old column (metadata-only), and only then remove the dual-write. Any index on the new column goes on with CREATE INDEX CONCURRENTLY per partition, since CIC is not supported on a partitioned parent: create the parent index with ONLY, build each child concurrently, then ALTER INDEX ... ATTACH PARTITION until the parent index becomes valid.

Worked solution (45 min)

  1. On a scratch cluster, build 10 partitions of 2M rows each and run a writer at a few thousand inserts/second.
  2. Run the naive type change and measure how long writes stall and how far ingest lag grows before killing it.
  3. Run the expand step with lock_timeout = '2s' while the writer runs, and observe a clean lock timeout and retry instead of a pile-up of blocked readers.
  4. Backfill in 5k-row batches and chart dead tuples and WAL generated per batch.
  5. Run the not-valid, validate, set-not-null sequence and confirm from pg_stat_activity and timings that nothing held an exclusive lock through a full scan.
  6. Add an index with CIC per partition plus ATTACH PARTITION and confirm the parent index reports valid only after the last attach.

Expected result: An ordered expand, dual-write, backfill, switch, contract plan with the lock named per step: metadata-only ADD COLUMN under a brief ACCESS EXCLUSIVE taken with lock_timeout; batched UPDATE backfill under row locks; CHECK NOT VALID, then VALIDATE under SHARE UPDATE EXCLUSIVE, then SET NOT NULL; CREATE INDEX CONCURRENTLY per partition with ATTACH PARTITION; and the dual-write-to-read switch identified as the step that cannot be reverted cleanly.

Verify it yourself

  • No step in the plan holds ACCESS EXCLUSIVE for longer than the configured lock_timeout.
  • The writer's error rate stays at zero through the rehearsal apart from the deliberate lock timeout.
  • After VALIDATE, SET NOT NULL completes in milliseconds on a table where the naive form scanned every row.
  • Freshly written rows agree on both columns for a full verification period before the old column is dropped.

Follow-up

  • A CREATE INDEX CONCURRENTLY fails halfway through the partition list. What state is the table in, how do you detect it, and what do you run?
  • The producer computes quantity itself. What happens to a request already in flight when the dual-write deploy lands, and does it matter?
  • Give two queries that prove the backfill is complete: one cheap enough to run every minute, one authoritative.

Enforce a concurrent-run quota that survives simultaneous requests

Medium · isolation levels · write skew · quotas · lock contention

A plan allows at most 20 concurrently running rows in job_run per tenant. The table holds run_id, tenant_id, workspace_id, status (queued, leased, running, succeeded, failed, timed_out, cancelled, lost), lease_token, leased_until, started_at and finished_at. Today the service runs select count(*) from job_run where tenant_id = $1 and status = 'running', compares the result to 20, then inserts. Under load a tenant exceeds the cap by exactly the number of concurrent requests. Name the anomaly, say which isolation levels do and do not prevent it, and give a version that holds, as SQL.

Approach

  1. Name it: write skew. Each transaction reads a predicate (the count of running rows), neither modifies what the other read, and both then insert rows that jointly violate an invariant no single row expresses. Read committed permits it. So does repeatable read, because snapshot isolation's first-updater-wins check fires only on conflicting row updates, and these are inserts touching disjoint rows.
  2. Enumerate the fixes with their real costs. SERIALIZABLE works: PostgreSQL's SSI tracks the predicate read and aborts one transaction with SQLSTATE 40001, which obliges the caller to retry and makes the abort rate rise with contention on a hot tenant. Folding the predicate into the write as insert ... select ... where (select count(*) ...) < 20 narrows the race to the statement's snapshot but does not close it under read committed.
  3. Give the version that holds at read committed: serialise on a row both transactions must touch. update tenant_concurrency set running = running + 1 where tenant_id = $1 and running < 20 returning running updates zero rows when the cap is reached, and zero rows is the rejection. This works because at read committed a blocked UPDATE re-evaluates its WHERE clause against the newly committed row; at repeatable read the same statement raises a serialisation error instead, so the isolation level changes the calling contract.
  4. State the cost you just bought. That row is now a per-tenant serialisation point, so admission throughput for the tenant is bounded by one divided by the lock hold time; at a 2 ms hold that is roughly 500 admissions/second. Keep the critical section to the single UPDATE, with no network call or scheduling decision inside the transaction, and decrement in the same transaction that writes the terminal status.
  5. Close the leak the status enum implies: a run can end as lost, so a crashed worker otherwise consumes a slot forever. Reconcile on a schedule against status = 'running' and leased_until < now(), and treat the counter as a fast path over job_run, which stays the system of record.

Follow-up

  • Write the retry loop for the SERIALIZABLE version. What does the caller see when it keeps aborting, and what bounds the retries?
  • Two regions each keep a counter. What is the effective cap, and what does admission do when the counter store is unreachable?
  • The cap changes mid-flight on a plan upgrade. Do running jobs get killed, and what does the counter row look like during the change?

Create an architecture for a real-time notification delivery engine that guarantees at-least-once message delivery across multiple mobile and web channels.

Medium · system design and architecture

Approach

  1. Name the failure you are designing for, then the recovery path.
  2. Choose a partition key and say what query it makes expensive.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • What would you drop to keep the system up under load?
  • How does this behave when that dependency is down for an hour?

Design a high-throughput ad scraping and analytics pipeline capable of ingesting external web data and serving real-time reporting dashboards.

Medium · system design and architecture

Approach

  1. Choose a partition key and say what query it makes expensive.
  2. State the consistency you need, and where you are willing to be stale.
  3. Fix the scope first: who calls this, how often, and what they do when it fails.

Follow-up

  • What breaks first when traffic grows ten times?
  • What would you drop to keep the system up under load?

Leasing sandboxed job runs without double execution

Medium · leases · fencing · state machines · metering

job-runner leases work from a queue and executes customer workloads in sandboxes with CPU, memory, wall-clock and egress limits. Run durations span 200 ms to 30 minutes, thousands are concurrent, and job_run.status must reach exactly one terminal state among succeeded, failed, timed_out, cancelled and lost. A worker can pause 45 seconds for garbage collection, or be briefly partitioned, after its sandbox has exited but before it writes the outcome. Design the lease duration, the fencing, and timeout ownership, and state how billable_seconds is computed for a run whose worker never returns.

Approach

  1. Resolve the lease-duration conflict rather than picking a compromise. A lease shorter than the longest legitimate run re-dispatches work that is still executing; a lease long enough for a 30-minute run leaves a crashed 200 ms run undetected for half an hour. Use a short lease - about 30 seconds - renewed by a heartbeat every 10 seconds while the supervisor is alive, each renewal pushing leased_until to now() + 30 s. Detection latency is then one lease, not one heartbeat: a dead worker's last successful heartbeat landed at most 10 s before it died, so leased_until expires 20 to 30 s after the death, plus whatever interval the reaper scans on. What the 3:1 ratio of lease to heartbeat buys is headroom - one or two missed heartbeats change nothing, and a stall only becomes a re-dispatch once it outlives the lease remaining when it began, between 20 and 30 s here. Both costs follow from those two numbers: heartbeat write load proportional to concurrent runs (3,000 runs at a 10-second interval is about 300 updates/second), and any pause longer than the lease - the 45-second GC - re-dispatching a run that is perfectly healthy.
  2. Fence the outcome write so the store, not the worker's memory, arbitrates: UPDATE job_run SET status = $1, finished_at = $2, exit_code = $3 WHERE run_id = $4 AND status = 'running' AND lease_token = $5, and require exactly one row affected. Zero rows means this worker was fenced while it was paused, and its correct behaviour is to discard the result, not to retry - the retry is how a resumed worker overwrites a newer attempt.
  3. Make the status machine monotone with a check constraint or a trigger so no terminal row can move, and model a retry as a new row with parent_run_id set and attempt incremented rather than a reset of the old one. That is what keeps how many times did this actually execute answerable afterwards and keeps each attempt's resource usage attributable to itself.
  4. Put the wall-clock timeout in the supervisor, never in the workload: a customer-supplied program asked to time itself out will not. Distinguish timed_out, which the supervisor observed and caused, from lost, which nobody observed at all, and leave exit_code null unless the supervisor actually saw the process exit. Recording lost as failed asserts an outcome no one witnessed and then bills and retries on that assertion.
  5. Decide billable_seconds for the unobserved case explicitly, because it cannot be derived. It is not now() - started_at, which bills queue and pause time the customer never consumed. The two defensible policies are leaving it null while status = 'lost' and billing nothing, or flooring it at the last heartbeat's observed running time as a stated lower bound; pick one and write it down. Add a per-tenant concurrency cap on sandbox slots so one tenant cannot occupy the whole fleet while these questions are being answered.

Worked solution (30 min)

  1. Draw the timeline: last successful heartbeat at t, sandbox exits at t, worker pauses from t to t+45 s, so leased_until = t+30 s and the lease expires there. Mark where the second worker starts and where the first worker's write arrives, then say how short the pause would have had to be to change nothing.
  2. Write the fenced terminal update and state the expected rows-affected in both the healthy case and the fenced case.
  3. Size the heartbeat: 3,000 concurrent runs at a 10-second interval is about 300 row updates/second on job_run - decide whether that lands on the same table and what it contends with.
  4. Write the billable_seconds rule for each terminal status and say which status leaves it null.

Expected result: A short lease of roughly 30 seconds renewed by a 10-second heartbeat, so detection latency is one lease - 20 to 30 seconds after a worker dies, plus the reaper's scan interval - independent of the 200 ms to 30 minute run spread, with one or two missed heartbeats tolerated rather than fatal; a terminal write fenced by AND status = 'running' AND lease_token = $x where zero rows affected means discard; retries as new rows with parent_run_id; a supervisor-owned wall-clock kill that separates timed_out from lost; billable_seconds left null while status = 'lost', or floored at the last heartbeat as a stated policy, never inferred from wall-clock.

Verify it yourself

  • In your timeline the fenced write must affect zero rows. If it affects one, the lease alone was doing the work and the token adds nothing.
  • At 3,000 concurrent runs and a 10-second heartbeat, check 300 updates/second against the other write load on that table before accepting the interval; if it is the dominant writer, the heartbeat belongs somewhere else. Check the ratio too: at lease = 3 x interval a lost heartbeat is survivable, whereas at lease = interval every scheduling hiccup is a double execution.
  • State whether a duplicate execution is billed, then confirm that summing billable_seconds across a parent and its retry matches that stated policy rather than matching it by accident.

Follow-up

  • A worker returns from a 45-second GC pause and writes succeeded. Walk through what the database does statement by statement.
  • Heartbeats are now a few hundred writes a second against job_run. How do you keep that off the path that matters, and what do you lose by moving it?
  • The re-dispatched workload has an external side effect the first attempt already performed. What does your design owe the customer, and what can it not fix?

Analyze a broken state management routine in a web application framework and correct asynchronous race conditions causing intermittent test failures.

Medium · debugging and production issues

Approach

  1. Check the instrumentation before believing the symptom.
  2. Say what evidence would prove you wrong, then go and look for it.
  3. Establish what changed and when, before forming any theory.

Follow-up

  • What would you look at first, and what would it rule out?
  • What would you add now so this is faster to diagnose next time?

Review a legacy utility module with cryptic variable naming, trace the step-by-step execution path, and fix underlying edge-case bugs under strict time constraints.

Medium · debugging and production issues

Approach

  1. Pick a bisection that eliminates candidates whichever way it turns out.
  2. Establish what changed and when, before forming any theory.
  3. Say what evidence would prove you wrong, then go and look for it.

Follow-up

  • How would you tell a cause from a coincidence here?
  • What would you add now so this is faster to diagnose next time?

Suggested practice checklist

For someone fluent in a dynamic language who has shipped real work but has never had to say what the runtime is doing underneath. The week is built on measuring and deliberately breaking things, because the questions that expose this background are the ones where the interviewer asks why a second time.

Day 1 — Progressive string and bracket problems

  • Solve the reported bracket problem as a three-part ladder: validate nested pairs, report maximum depth, then apply custom rules. Keep part one's code intact
  • As your own practice extension of the rules step, support custom bracket pairs from a lookup table without changing the part-one logic
  • Practise Find Valid Parentheses in String and Count Parentheses in String from the bank, adding a follow-up rule of your own to each
  • Solve Longest Substring Without Repeats and state its complexity before coding

Deliverable: One file with all three parts of the bracket ladder passing tests you wrote, with a complexity note for each part.

Day 2 — Grids, matrices and in-memory data structures

  • Practise Grid BFS and Sorting and 2D Board Replace Repeats, then extend one of them with obstacles that change between queries
  • Design an Integer Tracker from the bank: pick the structure from the required operations and state the cost of each
  • Build an in-memory structure for time-series event counts that supports insert, delete and 'is this key over threshold' checks, and test the boundary cases

Deliverable: Two grid solutions with written edge cases, plus a tracker class with a table of operation costs.

Day 3 — Event streams and rule-driven pipelines

  • Solve the reported problem of grouping and sorting user activity events by rule definitions: parse the rules first, then apply them in one pass
  • Practise Pipeline Based on Commands from the bank, adding one new command without touching the others
  • Work through the webhook header parsing worked exercise on this page as a timed single-pass parsing drill

Deliverable: A rule-driven event grouper with tests for empty input, conflicting rules and ties in sort order.

Day 4 — Repository debugging with failing tests

  • Set up a small open-source project, have someone plant three bugs in a string or cipher routine, and fix them while narrating each hypothesis first
  • Practise running a single test from the terminal in your framework until you can do it without looking anything up
  • Practise Find Bugs in Python Code from the bank and write a one-line root cause per bug before fixing

Deliverable: A written log per bug: failing test, hypothesis, evidence, one-line fix, and confirmation that the full suite passes.

Day 5 — Async races and safe refactoring

  • Reproduce an async race in shared state (two awaits updating the same object), make it fail every time, then fix it and explain why the fix holds
  • Take a long processing function, fix one planted bug first, then split it into testable functions without changing its public signature
  • Practise Optimize a Slow Application from the bank: measure first, then name the change and how you would confirm it helped

Deliverable: A fixed race with a test that used to fail intermittently, and a before-and-after refactor with the same passing tests.

Day 6 — System design with schema and SQL

  • Design the campaign manager schema and write two cohort-targeting queries, naming the index each one uses
  • Design the notification delivery engine with at-least-once delivery, including the dedup key and the worker-crash walkthrough
  • Sketch the ad scraping and analytics pipeline using the bank's Ad Scraping and Web Scraper questions, then work through the design and SQL worked exercises on this page

Deliverable: One schema with two queries and one delivery design with written failure cases, each on a single page.

Day 7 — Behavioral stories, recruiter pitch and a mock loop

  • Write stories for the reported prompts: speed versus maintainability, ambiguous requirements, a production incident, critical code review feedback, and a disagreement with a senior engineer
  • Rehearse the recruiter-call project walkthrough and your compensation answer
  • Run a back-to-back mock of one progressive coding problem, one debugging exercise and one design prompt, switching approach between them

Deliverable: Five behavioral stories with a decision point each, and notes from the mock loop on which round needs more work.

Scope is read from what you decided

Candidates describe a behavioral interview with an engineering manager about past experience and fit. The reported prompts ask for specific decisions: a trade-off you made, how you handled unclear requirements, an incident you owned through to prevention, and feedback you acted on. Prepare stories that start at the decision point and say what you knew at that moment.

  1. Describe a past project where you had to make a difficult architectural tradeoff between speed of delivery and long-term maintainability.
  2. How do you approach working with ambiguous technical requirements when collaborating with cross-functional product teams?
  3. Explain a scenario where you received critical technical feedback during a code review and how you adapted your implementation as a result.
  4. Tell me about a time you disagreed with a senior engineer's architectural decision. How did you present your counter-proposal and reach a resolution?

Walk through a time when a major bug or production incident occurred in your deployed application. How did you diagnose, resolve, and prevent it from recurring?

Medium · behavioural and engineering judgement

Approach

  1. Close with what you would do differently, concretely.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. 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?

Own the incident where invoices undercounted metered usage

Medium · incident response · at-least-once · billing correction · postmortem

A metering consumer acknowledged each batch before committing the fold into usage_rollup_hourly. A rolling deploy restarted consumers mid-batch for two hours; roughly 1.4M usage_event rows were acknowledged and never folded, and 61 invoices sealed against the resulting rollups before anyone noticed. Take the owner's role. Describe an incident of comparable blast radius you owned: how it surfaced, the query that sized the loss, what you stopped first, and how the money was corrected. Give a wall-clock timeline and one thing you got wrong while it was still live.

Approach

  1. Open with the invariant that broke and the direction of the error, because they determine everything else: acknowledging before committing makes the consumer at-most-once, so this loses events rather than duplicating them, and loss raises no error anywhere. A listener who hears 'we lost revenue silently' knows immediately why detection took two hours.
  2. Size it with a stated reconciliation rather than an adjective: sum(quantity) from usage_event grouped by (tenant_id, sku, hour of occurred_at) over the window, against usage_rollup_hourly.quantity_sum on the same keys, filtered to environment='production' because staging and sandbox are metered but not billed. Then bisect by hour and tenant until single cells explain the gap. Say how long that ran and whether a replica could serve it while the incident was live.
  3. Separate mitigation from fix and say which came first. Mitigation is holding the sealing job, because a sealed row is frozen by design and every minute of sealing converts a recoverable rollup into an invoice correction. The fix is moving the acknowledgement after the commit, which re-introduces duplicates that the dedup check on (tenant_id, idempotency_key) must now absorb.
  4. State the correction path in the domain's own terms: sealed periods are never edited, so each affected tenant gets an adjustment line on the next invoice with kind='adjustment' and voided_by_line_id pointing at the line it reverses, priced against the same rate tier and carrying the watermark it priced against. That is four separate numbers — tenants affected, minor units, the cycle the adjustment lands in, and when customers were told.
  5. Close on one prevention control with its cost, not five: a per-hour reconciliation comparing raw sum to rollup sum that pages above a threshold. Name the threshold and the false-page rate you accepted, because a detector nobody will keep staffed is not prevention.
  6. Name a mistake you made inside the response window — the wrong first hypothesis, a mitigation that made it worse — rather than a design mistake from six months earlier. That is the part candidates rehearse away and interviewers weight heavily.

Follow-up

  • Your fix moves the acknowledgement after the commit. What breaks now, and what absorbs it?
  • One undercharged tenant has since churned. Do you bill them, and who decides?
  • How would you have caught this in ten minutes instead of two hours, and what would that detector cost you in pages per week?

Argue against failing open when the control plane is unreachable

Hard · revocation · cache staleness · fail-open vs fail-closed · influence

The gateway caches credential-to-context decisions with a sixty-second TTL. A design proposal says that when control-plane reads fail, pods should keep serving from expired entries indefinitely so a control-plane outage never becomes a product outage. You believe that converts every revocation into an unbounded one. Describe a design you argued against while it was still a live proposal: what you measured or modelled to make the case, what you conceded, who decided, and what happened afterwards. Say what would have changed your mind before the decision, not after it.

Approach

  1. Reframe it from a values argument into a bounded-staleness argument. Both sides already accept the cache; the disagreement is only about the ceiling on how long a revoked credential keeps authorising. Put a number on the table — serve stale for up to fifteen minutes, then fail closed — and make the other side argue against a number rather than against a principle.
  2. Bring arithmetic rather than adjectives: the rate of revocations with revoked_reason in ('suspected_leak','auth_version_bump'), the observed distribution of control-plane unavailability, and the product of the two, which is expected requests served by revoked credentials per outage-hour. At 30k requests/second the unbounded version is not a subtle exposure and the number says so.
  3. Concede the strong half of the opposing case first, because that is what buys you the room: failing closed turns one service's outage into a total outage across three regions, and a control plane doing tens of writes per second is not engineered to the gateway's availability target. A proposal you have not steelmanned reads as reflex.
  4. Propose the asymmetry that usually resolves this: stale entitlements cost bounded money (a quota fifteen minutes out of date over-serves by a computable amount), while a stale revocation costs unbounded access. Split the cached decision by what it authorises, give the two halves different staleness ceilings, and let the entitlement half fail open while the revocation half fails closed.
  5. State the propagation dependency plainly, since it is the part that is missed: validity is also derived from the principal's auth_version, so password reset and sign-out-everywhere flow through this same cache. A design that bounds staleness for explicit revocation and not for auth_version bumps has only fixed half of it.
  6. Say who decided, and what you did afterwards in either outcome: write the decision down with its number and a review date, and instrument the exposure you were worried about so the next round of the argument is settled by data instead of by seniority.

Follow-up

  • Publish-subscribe invalidation is lossy under a partition, and a TTL is the only hard bound. What TTL do you pick, and what does it cost you at 30k requests/second?
  • The key was revoked because it was found in a public repository. Does your answer change, and where does that urgency live in the design?
  • You lost the argument and six weeks later the failure you predicted happens. What do you say in the review, and what do you not say?