Interview guides · C3 AI
C3 AI Software Engineer Interview Guide
C3 AI Software Engineer interview guide: reported coding, system design, ML and behavioral questions, a 7-day prep plan, and worked practice exercises.
The 60-second brief
Role descriptions for C3 AI Software Engineers describe developing and deploying enterprise AI applications: working with complex datasets, designing architectures that scale, and working with product managers and data scientists to turn business requirements into technical specifications. Candidates report technical interviews on coding and system design, and reported questions include how supervised and unsupervised learning differ and how you would improve a model's performance. Prepare for both: standard data-structure coding and design problems, and short, concrete explanations of machine learning concepts.
This guide covers the question categories reported for the C3 AI Software Engineer role: data-structure coding, system design, AI and machine learning fundamentals, and behavioral questions. For each category it lists the reported questions and how to structure an answer. It also includes original practice problems with worked solutions (an online migration of a partitioned table, sealing hourly aggregates under late data, and an API error contract). These go deeper than the reported questions on data pipelines and API design.
Focus areas
- Bound blast radius with per-tenant concurrency limits
- Scope every query and cache key by tenant
- Keep money in integer minor units
Overview
Role descriptions for the C3 AI Software Engineer position describe building, deploying and maintaining software for enterprise AI applications. The listed work includes working with complex datasets, designing software architectures that scale, and working with product managers and data scientists to turn business requirements into technical specifications. Code review and improving the development process are also listed. The stated technical requirements are proficiency in Python, Java or C++, with familiarity with AI frameworks and cloud platforms as a plus.
Reported questions for this role fall into four groups. Coding questions are standard data-structure problems: DFS on a binary tree, checking whether a string has all unique characters, finding two numbers that add up to a target, and implementing a stack using queues. Design questions include a URL shortening service, a system for real-time data processing in an AI application, and a scalable API. Technical and domain questions ask about your experience with AI and machine learning, the difference between supervised and unsupervised learning, and how you would improve a model's performance. Behavioral questions cover working under pressure, prioritizing across deadlines and working in a team.
Bank questions tagged to this company and role go further in the same categories: Trapping Rain Water, graph BFS, a Graph + DP problem, bit manipulation, Go concurrency, and design prompts such as a Twitter-like platform, an elevator controller, a booking system, a hotel management system and a restaurant reservation system. No confirmed round order is available, so prepare every category and ask your recruiter which ones your interviews include.
Where to focus your preparation
Treat the sections below as preparation areas and confirm the format with your recruiter.
Preparation focus
No confirmed round sequence is available for this role. Candidates report an initial screening call, technical interviews on coding and system design, and behavioral interviews. Treat those as your preparation areas, and confirm the actual format, order and any language expectations with your recruiter. Reported questions touch three technical areas, so divide your technical preparation among them: data-structure coding (trees, hashing, stacks and queues, arrays and dynamic programming), system design (URL shortener, real-time data processing, scalable APIs, stateful booking-style systems), and practical machine learning fundamentals.
What to demonstrate
- Correct, tested code for standard data-structure problems, with complexity stated and corner cases handled
- System design reasoning for services like a URL shortener or a real-time data pipeline: components, data model, scaling path and failure handling
- Working knowledge of machine learning concepts such as supervised versus unsupervised learning and improving a model
- Clear, specific accounts of past work, prioritization and teamwork
How to prepare
- Solve each reported coding question out loud, then a harder bank variant such as Trapping Rain Water or Graph Traversal and BFS
- Sketch the URL shortener and the real-time AI data pipeline end to end, including rough numbers and failure paths
- Write short spoken explanations of supervised versus unsupervised learning and of how you would improve a model's performance
- Prepare specific stories for pressure, competing deadlines, teamwork and a project you owned
Where candidates lose points
Solving the reported easy coding problems without stating assumptions or complexity
Two-sum, unique characters and stack-with-queues look simple, so a missing clarification stands out. Before coding, ask the key questions. Can the two-sum input hold duplicates or negatives? May an element pair with itself? Should you return indices or values? Is the unique-characters string ASCII or Unicode, and is extra memory allowed? For the queue-based stack, decide which operation pays O(n) and say why. Then state the complexity of your solution and the brute-force baseline it improves on.
Skipping corner cases in tree, array and DP solutions
Before you declare a solution done, test empty input, a single element, all-equal values and the largest input size you were given. For DFS on a binary tree, handle a null root and say whether recursion depth is a risk on a skewed tree. Explain when you would switch to an explicit stack. For Trapping Rain Water, walk through arrays of length 0 to 2 and a strictly increasing array, where the answer is 0. Trace one small example through the code you actually wrote, not the code you meant to write.
Presenting a URL-shortener or real-time pipeline design with no numbers and no failure path
Start with requirements and a rough read/write ratio, then choose components. For the URL shortener, explain how keys are generated and how collisions are avoided, what is cached, and what happens when the key store is unavailable. For real-time data processing in an AI application, separate ingestion, processing and serving. Name where data can arrive late or twice and how the downstream model consumer handles it. The worked exercise on sealing hourly aggregates under late data practises this reasoning.
Staying vague on machine learning basics in a role building AI applications
Reported questions ask about your AI/ML experience, the difference between supervised and unsupervised learning, and how to optimize a model. Give each definition with a concrete example, such as labelled classification versus clustering unlabelled records. Answer the optimization question in order: confirm the metric and the baseline, check the data and features for leakage or class imbalance, then tune and regularize, and validate on held-out data. If your ML experience is limited, say so plainly. Then describe adjacent work you have done, such as the data pipelines or services a model depended on.
Answering pressure or prioritization questions with general habits instead of a decision
Pick one real situation. Name the competing items, the criterion you used to order them, what you dropped or renegotiated and with whom, and the result. A list of habits such as keeping a to-do list gives the interviewer nothing to probe, and it does not show how you make trade-offs.
Questions & practice
Implement a stack using queues.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
- 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?
- What is the worst case, and how likely is it on real data?
Given a list of integers, find two numbers that add up to a specific target.
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Write a function to determine if a string has all unique characters.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Solve a problem using depth-first search (DFS) in a binary tree.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Seal an hour under late data with bounded memory
Hard · watermarks · late-data · quantile-sketch · conditional-write
Metering ingest reads 256 partitions at 10,000 to 40,000 events/second. Events carry occurred_at and ingested_at, and during a producer replay the gap between them is hours. Seal each UTC hour once no more than 50 parts per million of that hour's eventual quantity can still arrive, using memory that does not grow with the size of the replay. Define the watermark, the lateness parameter and how you measure it, the structure holding open hours, and the write that performs the seal. State what an idle partition does to your watermark.
Approach
- Two clocks, two jobs. Bucket by
occurred_at, because that is the hour the customer is billed for, and advance the watermark oningested_at, because that is what the fold has consumed and whatsource_max_ingested_atrecords. Conflating them is what makes late data invisible. - The global watermark is the min over partitions of each partition's committed
ingested_at, not the max: the fold is trustworthy only as far as the slowest partition. The consequence is that one idle partition pins the watermark forever and nothing seals, so an idle partition must promote its watermark to wall clock after a stated idle timeout, and that timeout becomes a correctness parameter, because a partition that is slow rather than idle gets sealed past. - Choose the lateness L from the measured distribution of
ingested_at - occurred_at, weighted by quantity rather than by event count. The target is 50 ppm of the hour's quantity, and a replay is rare in events while carrying disproportionate mass, so an event-weighted quantile picks an L that is comfortably wrong at exactly the moment it matters. - Measure that quantile in bounded memory. A Greenwald-Khanna summary gives epsilon-approximate quantiles in O((1/epsilon) log(epsilon n)) space; a t-digest costs more per merge but has relative error that tightens at the tails, which is the half of the distribution you are reading at p99.99. Keep a separate summary per tenant class, because one tenant's batch importer is not the population.
- Hold open hours in a min-heap keyed by
hour_start. When the watermark advances, pop every hour withhour_end + L < Wand seal it: O(log H_open) per advance and O(1) amortised per event to touch its bucket. Memory is open hours multiplied by distinct(tenant, workspace, sku)keys, so cap the number of simultaneously open hours and spill the oldest intousage_rollup_hourlyasstatus='open'with arevisionbump. While an hour is open the row is upsertable, so the store is your overflow. - The seal itself is a conditional write:
update ... set status='sealed', sealed_at=now() where status='open' returning .... Two sealers race on every restart, and the loser must see zero rows and stop rather than write a second value. After the seal, an event for that hour is not an upsert but an adjustment, andsource_max_ingested_atis what proves it arrived afterwards.
Worked solution (40 min)
- Replay a day of events with a synthetic lateness distribution: 99.9% under two minutes, plus a 0.05% tail at four to six hours that carries 3% of total quantity.
- Compute the p99.99 lateness two ways, event-weighted and quantity-weighted, and put the two numbers side by side.
- Implement the min-heap of open hours with the watermark as the min over 256 partitions, then stall one partition for 20 minutes and observe what seals.
- Set the idle-partition timeout to 60 seconds, repeat the stall, and measure how much quantity arrives after the seal.
- Attempt the seal from two workers at once and confirm the conditional update lets exactly one through.
Expected result: The quantity-weighted p99.99 is hours larger than the event-weighted one. Choosing L from the event-weighted number lets roughly the tail's 3% of quantity land after the seal, 600 times the 50 ppm target. With the min watermark and no idle timeout, the stalled partition blocks all sealing; with a 60-second timeout, the stall is sealed past and its events arrive late.
Verify it yourself
- Post-seal quantity as a fraction of the hour's total is at or below 50 ppm at the chosen L, measured rather than assumed.
- The second sealer's update reports zero rows affected and writes nothing.
- Heap size equals the number of open hours and does not grow with the replay's event count.
Follow-up
- A replay starts during the sealing window for a period you are about to close. What do you do, and what is the customer-visible consequence of each option?
- Your measured quantity-weighted p99.99 lateness is six hours and the invoice must be issued at 02:00 UTC on the first. How do you reconcile those two numbers?
- How would you detect that L has drifted before it costs you an hour's quantity?
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
- 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 ... typerewrites 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. - Expand: add
quantity_numeric numeric(20,6)andenvironmentwith its default on the parent. Both are catalogue-only but both take a brief ACCESS EXCLUSIVE that cascades to partitions, so run each withlock_timeoutset 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. - 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.
- 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.
- Make NOT NULL cheap with the three-step form:
add constraint ... check (environment is not null) not valid(brief ACCESS EXCLUSIVE, no scan), thenvalidate constraint(SHARE UPDATE EXCLUSIVE, scans while reads and writes continue), thenset not null, which from PostgreSQL 12 uses the validated check and skips its own full scan. Do this per partition, then on the parent. - 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)
- On a scratch cluster, build 10 partitions of 2M rows each and run a writer at a few thousand inserts/second.
- Run the naive type change and measure how long writes stall and how far ingest lag grows before killing it.
- 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. - Backfill in 5k-row batches and chart dead tuples and WAL generated per batch.
- Run the not-valid, validate, set-not-null sequence and confirm from
pg_stat_activityand timings that nothing held an exclusive lock through a full scan. - 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.
Rebuild an hourly rollup with deduplication and late-arrival accounting
Hard · window functions · deduplication · event time vs ingest time · watermarks
From usage_event (event_id, tenant_id, workspace_id, environment, sku, quantity numeric(20,6), idempotency_key, occurred_at, ingested_at), produce the values usage_rollup_hourly should hold for one tenant over one day: per (workspace_id, sku, hour_start) the deduplicated quantity_sum, event_count and source_max_ingested_at, bucketed by occurred_at. Duplicates share (tenant_id, idempotency_key). Also report, per hour, the running total across the day and the share of quantity that arrived more than two hours after the hour began. Write the query, and state which duplicates a daily unique index cannot catch.
Approach
- Deduplicate in its own CTE before any aggregation, because a SUM cannot be un-summed:
row_number() over (partition by tenant_id, idempotency_key order by ingested_at, event_id) = 1. Include the tiebreaker. Without it the surviving row is non-deterministic when two duplicates share an ingested_at, and a rollup described as deterministically recomputable then disagrees with itself between runs. - Bucket on occurred_at and nothing else, and pin the timezone explicitly.
date_trunc('hour', timestamptz)truncates in the session's TimeZone setting, so the same query run by a session set to a non-UTC zone buckets differently; use the three-argumentdate_trunc('hour', occurred_at, 'UTC')on PostgreSQL 16 or later, ordate_trunc('hour', occurred_at at time zone 'UTC') at time zone 'UTC'before that. Filterenvironment = 'production'explicitly, since metering covers three environments and billing covers one. - Aggregate to the grain with
sum(quantity),count(*)andmax(ingested_at). The last is not decoration: it is the watermark the row consumed up to, and without it there is no way to prove afterwards what a number did and did not include. - Compute the late share inside the dedup-and-aggregate step as a conditional aggregate,
sum(quantity) filter (where ingested_at > hour_start + interval '2 hours'), then divide by the hour's total. Compute the running total as a window over the already aggregated rows:sum(quantity_sum) over (partition by workspace_id, sku order by hour_start rows between unbounded preceding and current row). Running either over raw rows puts the duplicates back. - Answer the index question exactly. The unique constraint is on (ingested_day, tenant_id, idempotency_key), because a unique index on a partitioned table must contain the partition key. It therefore deduplicates only within one ingest day and admits a duplicate whose retry crosses midnight or whose replay runs a week later. That is why this CTE dedups across the whole window being recomputed, and why the dedup horizon is a correctness parameter rather than a retention cost.
- Keep the numeric type all the way through. quantity is numeric so the sums are exact; a cast to double precision anywhere in this pipeline reintroduces drift that surfaces only as a few unreconcilable cents per tenant per month, long after the query is out of anyone's mind.
Follow-up
- A dispute forces the same recompute over 40 days for one tenant. What changes about the dedup CTE's memory use and the chosen plan, and what would you do about it?
- Two runs a minute apart return different quantity_sum values for an hour that is already closed. Give two mechanisms that produce that, and the single query that distinguishes them.
- Express the same rollup incrementally so it does not re-scan the day each time the watermark advances. What does the incremental version stop being able to answer?
Explain your approach to designing a scalable API.
Medium · system design and architecture
Approach
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
- State the consistency you need, and where you are willing to be stale.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
How would you architect a system to handle real-time data processing for an AI application?
Medium · system design and architecture
Approach
- State the consistency you need, and where you are willing to be stale.
- Name the read and write paths separately; they rarely have the same bottleneck.
- Choose a partition key and say what query it makes expensive.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
Can you explain the difference between supervised and unsupervised learning?
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Design a machine-readable error contract for the gateway
Easy · error taxonomy · retries · api contract
The edge gateway serves roughly 30k requests/second to SDKs and CI pipelines that retry automatically. Today every failure returns 500 with a prose message that clients string-match on. Design the error contract: the response body fields, and the status code for a malformed body, a revoked credential, a scope the credential lacks, a row belonging to another tenant, a reused idempotency key sent with a different body, an exceeded rate limit, and an unreachable dependency. For each, state whether the client may retry and on what schedule. Deliverable: the envelope schema plus the status-to-retry table.
Approach
- Split the envelope by audience: a stable
codestring for programs, amessagedocumented as human-only and free to change, arequest_idthat joins to gateway logs, and adetailsarray for per-field problems. The code list is an enum that only ever grows. - Assign status by who has to change something: 400/422 for the caller's bytes, 401 for a credential that no longer authenticates, 403 for a scope or entitlement, 404 rather than 403 for a row in another tenant because 403 confirms the identifier exists, 409 for an idempotency conflict, 429 for a limit, 503 for a dependency.
- Derive retryability from the method and the idempotency key rather than from the status: a 5xx or a timeout is an unknown outcome, not a failure, so GET/PUT/DELETE may be retried under HTTP semantics and POST only when it carries an idempotency key.
- Put the schedule in the response: Retry-After on 429 and 503 overrides the client's own backoff; otherwise capped exponential backoff with full jitter, sleeping uniformly in [0, min(cap, base * 2^attempt)], bounded by a total attempt budget so retries expire before the caller's deadline.
- Write the negative rules into the published contract: clients must never parse
message, must tolerate unknowncodevalues by falling back to the status class, and a code's meaning is never redefined once shipped.
Worked solution (20 min)
- Write the envelope as a JSON schema with four top-level fields and say which are guaranteed present on every error.
- Fill a seven-row table: condition, status, code string, retryable yes/no, and the schedule or the reason retrying cannot help.
- For each non-retryable row, write the one thing the caller must change (bytes, credential, plan, key) so nothing is marked non-retryable without a remedy.
- Add the unknown-outcome row for timeouts and 5xx separately from the other rows, and give it an action other than 'treat as failed'.
- Write two sentences of client guidance: honour Retry-After when present, apply full jitter otherwise, and stop at the attempt budget.
Expected result: An envelope of { code, message, request_id, details[] } plus a table in which only 429 and 503 are unconditionally retryable, 5xx and timeouts are retryable only for idempotent methods or keyed POSTs, and every other row names a caller-side change.
Verify it yourself
- No row requires the client to read
messageto decide what to do. - The cross-tenant row returns 404 and the justification mentions existence disclosure.
- Timeout appears as its own outcome, distinct from both success and failure.
- Retry-After is stated as authoritative over any client-side backoff calculation.
Follow-up
- A customer reports they retried a 500 from POST /v1/runs and ended up with two sandboxes billed. Whose bug is it, and what in your contract permits their reading?
- You need to add a new error code next quarter without a version bump. What did the v1 contract have to say for that to be non-breaking?
Invoice detail latency triples after an ORM relationship refactor
Easy · n+1 query · orm · latency regression
An invoice detail endpoint returned in 40 ms at p99 last week. After a refactor replaced a hand-written join with ORM relationship access it returns in 1.4 s, and the regression grows with the number of invoice_line_item rows on the invoice. Database CPU rose, but no statement in the slow-query log exceeds 3 ms. You have request traces with per-span SQL, the ORM statement log, and a staging copy of the data. Produce an ordered diagnostic checklist, the measurement that confirms the cause before any code change, and the fix.
Approach
- Count statements per request before reading any statement duration. A slow-query log hides this class by construction, because every individual query is fast and only their number is wrong; take one trace and count SQL spans.
- Establish proportionality rather than asserting it: sample invoices with 5, 20, 60 and 200 line items and plot statements per request against line count. A straight line of slope 1 through an intercept of one or two identifies a lazy relationship load, and no index or cache would move that line.
- Locate the emitting attribute access in the refactored code and check whether the same shape repeats one level deeper, for instance a tax or adjustment collection hanging off each line, which turns the cost quadratic.
- Fix with a bounded statement count: either one join that fetches invoice and lines together, or two statements where the second is WHERE invoice_id = $1 AND tenant_id = $2. Keep tenant_id in the predicate so the read stays tenant-scoped even though invoice_id already implies it.
- Choose between the two deliberately: the join duplicates the wide parent row across N children on the wire, the two-statement form avoids that for one extra round trip. Prefer the join for narrow parents and the split for wide ones.
- Pin it with a per-request statement-count assertion in a test that varies line count, because a latency assertion passes on a small fixture and would not have caught this.
Follow-up
- The endpoint now also needs per-line tax rows. Show the shape that keeps statement count constant instead of reintroducing the same defect one level down.
- How does this change if a transaction-pooling proxy sits between the service and the database, so each statement may land on a different backend session?
- The same page paginates invoices with LIMIT and OFFSET. Why is that a second, independent defect, and what replaces it?
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 — Hashing and strings: two-sum and unique characters
- Solve two-sum with a hash map in one pass, then solve the sorted-input variant with two pointers. State the time and space trade-off of each.
- Solve unique characters three ways: a set, a fixed-size boolean array or bitset for ASCII, and sort-then-scan when extra memory is not allowed. Say which constraint picks each one.
- Work the Coding Hashmap and Experience bank question. Prepare a short account of a real case where a hash map fixed a performance problem in your own work.
Deliverable: Three solved problems, each with its complexity and the clarifying questions you asked before coding.
Day 2 — Trees, graphs and stack-with-queues
- Implement recursive and iterative DFS on a binary tree in pre-, in- and post-order. Then solve one DFS problem such as path sum or maximum depth.
- Implement BFS with a visited set for the Graph Traversal and BFS bank question. Then work the Traverse a Red-Black Tree bank question, practising pre-, in- and post-order traversal.
- Implement a stack with two queues and with one queue. Write down which operation is O(n) in each version and when you would prefer one over the other.
Deliverable: Traversal and stack implementations that pass tests for an empty tree, a single node and a fully skewed tree.
Day 3 — Arrays, dynamic programming and corner cases
- Solve Trapping Rain Water with prefix maxima first, then with the O(n) time, O(1) space two-pointer version. Explain why it is safe to advance the pointer on the smaller side.
- Work the Arrays and Dynamic Programming bank question and one Graph + DP problem. Define the state, the transition and the base case before writing code.
- Solve the Bit Flip Function Coding question and test it on empty or minimal input, all-zero and all-one inputs, and the largest allowed size.
- For every solution today, write the corner-case list before you run the code.
Deliverable: Three solutions, each with a written corner-case list and the complexity stated.
Day 4 — Classic system design: URL shortener and scalable API
- Design a URL shortening service. Cover requirements, the estimated read/write ratio, key generation, storage, caching and the redirect path, and say what fails first under load.
- Answer the scalable API question: resource design, pagination, versioning, rate limiting, idempotency on writes and error handling.
- Work the worked exercise 'Design a machine-readable error contract for the gateway' and compare your status-to-retry table with the reference.
Deliverable: Two one-page design sketches and a completed error contract table.
Day 5 — Real-time data processing and stateful designs
- Design a system for real-time data processing in an AI application: ingestion, stream processing, storage of features or results, and serving. Mark where data can arrive late or duplicated.
- Work the worked exercise 'Seal an hour under late data with bounded memory' and check your watermark definition against the reference.
- Sketch one stateful design from the bank: a booking system, a restaurant reservation system or an elevator controller. Focus on the data model and how you prevent conflicting requests or double-booking.
Deliverable: A pipeline diagram with failure handling and one stateful design with its data model.
Day 6 — ML fundamentals, concurrency, testing and databases
- Write short answers to the reported domain questions: your AI/ML experience, supervised versus unsupervised learning with an example of each, and how you would improve a model's performance.
- Use the Go Concurrency: Threads and Mutexes bank question to review goroutines, mutexes and channels, and how you would detect a race.
- Review end-to-end versus unit and integration tests. Then work the Database Design and Tradeoffs question and the worked exercise 'Migrate a live partitioned event table without blocking ingest'.
- Prepare answers to the reported problem-solving questions on debugging a complex issue, gathering requirements and keeping code maintainable. Practise with the ORM latency debugging drill.
Deliverable: Written answers to each domain question and a completed migration plan that names the lock taken at each step.
Day 7 — Behavioral stories and a full mock
- Prepare one specific story each for working under pressure, prioritizing multiple deadlines, working effectively in a team, owning a high-stakes project, and a strength and growth area.
- Prepare a summary of your previous work experience that connects to data-heavy or AI-adjacent software, without overstating your ML exposure.
- Run a mock with a partner: one reported coding question, one design question and two behavioral questions. Then list what you would change in each answer.
Deliverable: Five behavioral stories and written notes from one full mock session.
The bug you decided not to fix
The reported behavioral questions for this role are standard ones: working under pressure, prioritizing across deadlines and working in a team, plus a reported technical question about a hard problem you solved. For each, give one real situation, what you decided and why, and a result you can state concretely. Keep one story ready that shows you owning a project end to end. Keep another that shows how you work with non-engineers such as product managers or data scientists, since role descriptions list that collaboration.
- How do you prioritize tasks when faced with multiple deadlines?
- Can you give an example of how you worked effectively in a team setting?
- Describe a challenging technical problem you encountered and how you solved it.
- Tell me about a high-stakes project you owned: the decisions you made, the stakeholders involved and the impact.
- What is a professional strength of yours, and a weakness you are actively working on?
What is your experience with AI and machine learning technologies?
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Pick a story where you made the decision, not one where you watched it.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
Describe a time when you had to work under pressure. How did you handle it?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- What did you decide not to do, and why?
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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?