Interview guides · Akur8
Akur8 Software Engineer Interview Guide
Akur8 Software Engineer interview guide: five reported rounds, Python and OOP live coding, system design, domain questions and a 7-day prep plan.
The 60-second brief
Candidates describe Akur8 as a company that sells a pricing platform to insurers. The platform uses AI to automate risk modelling and keeps its models transparent. A Software Engineer, as candidates describe the role, builds the numerical computing features and the backend systems that run actuarial models in production. The work is done alongside data scientists, actuaries and product managers, and includes turning a new pricing model into a service that other people can rely on.
This guide is for Software Engineer candidates, mid-level and senior, preparing for the reported Akur8 loop. It covers the five stages candidates describe, the twelve reported questions (object-oriented live coding, system design and domain logic), the habit of clarifying vague prompts before coding, and the technical conversations that can surface in the final culture fit interviews. It stays with Python and general backend skills, because those are what the reported requirements list. C++ does not appear in those requirements, so a candidate for a numerical-engine team should ask the recruiter whether C++ or Python extension work comes up, and whether to bring a compiled-language option for the online assessment.
Candidates report five rounds over roughly 4-6 weeks: an HR phone screen, an online assessment, live technical rounds, system design rounds and culture fit interviews. System design depends on seniority, and the last stage often involves senior stakeholders.
Focus areas
- Coding Interviews
- Python
- Problem Solving
Overview
The reported questions fall into three groups of four. The first group is object-oriented live coding: a class hierarchy for a rule-based pricing engine, parsing and validating an insurance JSON payload, refactoring a legacy Python script toward SOLID, and finding anomalies in a time series. The second is system design: running heavy mathematical models asynchronously, an API that serves pricing data to external clients, zero-downtime schema migrations, and a data flow for a real-time actuarial dashboard. The third is domain logic: clarifying questions for a premium calculation, how AI can price insurance transparently, optimising a numerical Python snippet, and a behavioral story about turning a vague requirement into an architecture.
Candidates say the common thread is ambiguity. Prompts are reported to arrive incomplete on purpose, for example a risk calculation with some variables missing, or an input format that changes under you. Preparation therefore has two halves: the technique for each question, and a repeatable opening move. That move is to name the inputs and outputs, list what is missing, state the assumptions you will code against, and confirm them before you type. Day 4 of the plan drills exactly this on the premium function and the numerical snippet.
Two logistics points shape practice. Candidates report coding on their own machine with a shared screen instead of a hosted editor, so rehearse in the environment you will use. And candidates report that the culture fit stage can include senior stakeholders, a technical deep dive into a past project, or an impromptu code review. Treat that stage as technical: have two project stories with the architecture choices and business impact ready, and enough insurance-pricing vocabulary to discuss the product without bluffing.
The Akur8 interview process
Candidates describe five stages over about 4-6 weeks. The exact sequence can shift with location and seniority, so confirm your own schedule with the recruiter.
HR Phone Screen
Candidates report an opening call about your background, your motivation and the highlights of your resume. It is the cheapest round to prepare for and the easiest to underrate. Prepare the story so you can reuse it in later rounds, and approach the call as a short pitch: what you built, in which stack, and why a pricing platform for insurers attracts you.
What to demonstrate
- How clearly you describe your background
- Your motivations for this company and this role
- Which resume highlights you choose to lead with and how well you explain them
How to prepare
- Write a two-minute walk through your resume that puts Python, backend and data-heavy work first.
- Prepare a specific, honest answer to why insurance pricing, after reading about transparent AI in pricing.
- List questions about the rest of the loop, in particular which system design rounds apply at your seniority.
Online Assessment
Candidates report an online assessment on a platform such as CodinGame, mixing multiple-choice questions with algorithmic coding tasks. Because the format is multiple choice plus algorithmic tasks, prepare practically: fast, correct code in a language you know well, and read-input and print-output habits that do not cost minutes.
What to demonstrate
- Programming fundamentals through multiple-choice questions
- Working algorithmic solutions to coding tasks
- Accuracy and efficiency of code written on a hosted platform
How to prepare
- Solve timed problems on a CodinGame-style platform until its input and output conventions feel automatic.
- Refresh Python complexity facts: list append versus insert, dict and set lookups, heapq, collections.deque and sorting with a key.
- Practise sliding-window, hashing and sorting problems; the time-series anomaly question is good practice for the window technique.
- Review basics likely to appear as multiple choice: Big-O, OOP terms, and common Python behaviours such as mutable default arguments.
Live Technical Rounds
Candidates report live coding with senior engineers, often two of them, on object-oriented and algorithmic problems. They also report that you code in your own local environment and share your screen, and that the session is not tightly guided, so you are expected to lead. The question types practised for this stage are the class-hierarchy design, the JSON parse-validate-process task, the SOLID refactor and the algorithmic anomaly problem.
What to demonstrate
- Class design: inheritance, encapsulation and where responsibilities sit
- Algorithmic problem solving and the complexity of what you write
- Code hygiene in a live setting
- Depth in Python itself, which candidates link to numerical computing roles
How to prepare
- Rehearse on your own machine with a fresh virtual environment, pytest and a blank project, sharing your screen the whole time.
- Build a small rule-based pricing engine with a rule interface, concrete rules and an ordered pipeline, narrating each design choice.
- Practise refactoring a messy script: write characterisation tests first, then extract classes and functions in small steps.
- Say the complexity and two edge cases aloud after every solution.
System Design Rounds
Candidates report dedicated system design rounds covering backend and frontend architecture, depending on seniority. The reported design questions cover asynchronous model execution, a pricing API for external clients, zero-downtime schema migrations and a real-time analytics dashboard. Approach each one by fixing the scope and the numbers, drawing the read and write paths separately, and naming what breaks first.
What to demonstrate
- Whether long-running numerical jobs are kept off the request path
- How you justify storage and caching decisions for analytics-heavy features
- APIs that serve complex data to a frontend
- Data integrity during change, such as live migrations
How to prepare
- Design the asynchronous model-run flow: submit endpoint, job queue, workers, status polling, result store, retries and idempotency keys.
- Sketch the pricing API for external clients: authentication, versioning, rate limits, caching and pagination.
- Compare polling, server-sent events and WebSockets for the real-time dashboard, with a pre-aggregation layer behind it.
Culture Fit Interviews
Candidates report culture fit and motivation interviews, often with senior stakeholders such as a CTO or CEO, and say these can include founders or lead data scientists. They also report that the conversation can turn technical: mathematical intuition about insurance risk, a deep dive into a past project, or a code review of a snippet during a casual talk. Prepare as for a technical round with a business audience.
What to demonstrate
- Your understanding of the product and the mathematics behind insurance pricing
- How you describe architectural decisions and their business impact
- How you work with data scientists and other non-engineers
How to prepare
- Prepare two project stories, each with the problem, your architectural decisions, the alternatives you rejected and a business outcome.
- Learn the basics of pricing: premium as expected claims cost plus expenses and margin, frequency versus severity, and why explainable models matter to regulators.
- Practise reading an unfamiliar Python snippet aloud, saying what it does and where it could be wrong or slow.
- Write your answer to the vague-requirement story and rehearse it in under three minutes.
Where candidates lose points
Coding the premium function before asking anything
Candidates report that prompts such as a premium from user risk arrive incomplete on purpose. Open with a short checklist: which risk factors and their types, what happens with missing values, rounding and currency, minimum and maximum premium, who consumes the result, and how it must be audited. Then state your assumptions and code against them.
Arriving at a live round with an unprepared local setup
Candidates report coding on their own machine with a shared screen. Create a clean virtual environment, install pytest, NumPy and Pandas, open a blank project, close personal tabs, and test the screen share with a friend the day before.
Refactoring a legacy script by rewriting it
Write characterisation tests that pin current behaviour, then make small steps: extract functions, separate parsing from calculation from output, inject dependencies, and introduce a class only where it removes a conditional. Say which SOLID principle each step serves. A deep inheritance tree for a pricing engine is usually worse than composable rule objects.
Designing model execution inside the request path, or a migration as a single step
For heavy computation, return a job id, queue the run, process in workers with timeouts, retries and idempotency keys, and expose status and results separately. For schema changes: expand with a nullable column, deploy dual-write, backfill in batches, verify, switch reads, stop old writes, then contract. Dual-writing must start before the backfill, or rows written in the gap never reach the new column. Call out lock risk on large tables.
Treating the culture fit round as a casual chat
Candidates report technical pivots there: maths, a deep dive on a past project, an impromptu code review. Know your own projects at the level of decisions and alternatives, learn the basic vocabulary of insurance pricing, and practise explaining a technical trade-off to a non-engineer in plain terms.
Questions & practice
Implement a method to parse, validate, and process a complex JSON payload of insurance data.
Medium · Object-Oriented Programming and Live Cod
Approach
- Split the work into three stages with their own errors: parse (json.loads raises json.JSONDecodeError, a subclass of ValueError), validate against a schema, then map to domain objects and process. Processing code should never see unvalidated dicts.
- Validate with a schema library (pydantic or jsonschema) or explicit dataclasses. Check required versus optional keys, null versus missing, types, ranges and enums. Note that isinstance(True, int) is True in Python, so a boolean slips through a naive int check.
- Collect every error with its path (for example policies[3].driver.birth_date) instead of stopping at the first. Decide up front whether one bad record rejects the batch or only that record.
- Parse money with json.loads(s, parse_float=Decimal) and dates with date.fromisoformat. Duplicate keys silently keep the last value; an object_pairs_hook can detect them.
- Complexity is O(n) in payload size. For payloads too large for memory, use a streaming parser such as ijson and validate record by record.
Follow-up
- The payload gains a new optional field next month: how does your validator handle unknown keys and schema versions?
- Should one invalid policy in a batch of 10,000 fail the whole request?
- How would you return validation errors to the caller so they can fix the data?
Refactor this legacy Python script to improve readability and adhere to SOLID principles.
Medium · Object-Oriented Programming and Live Cod
Approach
- Before changing anything, pin current behaviour with characterization tests: run the script on a few representative inputs and assert the outputs stay identical. Every refactor step must keep those green.
- Read for smells: one long main with global state, I/O mixed into calculations, magic numbers, duplicated blocks, bare except clauses that swallow errors, mutable default arguments (def f(x=[]) shares one list across calls).
- Apply single responsibility first: pull out pure functions for the computation, and keep file reading, parsing and printing at the edges behind an if name == "main" entry point.
- Add abstractions only where variation really exists. If inputs come from CSV and JSON, define a reader interface and inject it (dependency inversion), which also makes testing with fakes easy. Say where SOLID would be over-engineering.
- Finish with type hints, dataclasses instead of loose tuples or dicts, named constants and clear names. Walk the interviewer through it as small, separately reviewable steps.
Follow-up
- If you had time for only one change, which gives the most value and why?
- How do you show the refactored version produces exactly the same output as the original?
- Where would applying SOLID strictly make this script worse?
Write an algorithm to efficiently find anomalies in a time-series dataset.
Medium · Object-Oriented Programming and Live Cod
Approach
- Clarify what counts as an anomaly (a point spike, a level shift, a missing stretch), whether data arrives in batch or as a stream, whether timestamps are regular, and whether there is seasonality.
- Baseline: a rolling mean and standard deviation over a window w, flagging |x - mean| > k * std. Compute it from the previous w points (shift by one in pandas) so a spike does not inflate its own threshold. Avoid naive running sums of x and x^2: they suffer the same catastrophic cancellation as the one-pass variance in the numerical snippet question. Use a rolling Welford-style update (add the new point, remove the oldest) or recompute each window stably, which keeps O(n) without losing accuracy on large values.
- Make it robust: a rolling median and MAD (median absolute deviation, scaled by 1.4826 to match the standard deviation under normality) resist the outliers you are hunting. A sorted window with insort is O(w) per step; a SortedList or two heaps with lazy deletion give O(log w).
- For seasonal data, compare against the same point in the previous cycle, or decompose (for example STL) and threshold the residuals.
- Edge cases: the warm-up period with fewer than w points, zero variance on a flat series (division by zero), NaNs and gaps, irregular sampling. Say how you would choose k and w, using labelled anomalies and precision and recall if any exist.
Follow-up
- How would you make this streaming with constant memory per series, for example with an exponentially weighted mean and variance?
- The series has a strong weekly pattern: what changes?
- How do you pick the threshold when you have no labelled anomalies?
Design a system to execute heavy mathematical models asynchronously without blocking the main application thre
Medium · System Design and Architecture
Design a system to execute heavy mathematical models asynchronously without blocking the main application thread.
Approach
- Name the Python constraint: because of the GIL, CPU-bound pure-Python code in threads does not run in parallel, so heavy computation belongs in separate processes or workers. Many NumPy operations release the GIL, but not all of them.
- Core flow: the API validates the request, stores a job record (status queued), puts a message on a queue and returns 202 Accepted with a job id. Worker processes pull jobs, run the model and write results plus status. Clients poll GET /jobs/{id} or receive a webhook or push notification.
- Inside a single service, an asyncio app can offload work with loop.run_in_executor and a ProcessPoolExecutor. Arguments and results must be picklable, and large arrays are costly to copy (shared memory or passing a storage reference helps).
- Reliability: make tasks idempotent so retries are safe, key jobs by a hash of inputs plus model version to dedupe and cache, set timeouts, cap retries and send repeated failures to a dead-letter queue. Store progress for long runs and support cancellation.
- Scale and fairness: bound the queue length for backpressure, scale workers on queue depth, and separate interactive jobs from batch jobs (priority queues) so a large batch cannot starve a user waiting on screen.
Follow-up
- A worker crashes halfway through a job: what happens to the job, and how does the client find out?
- How would you split one very large model run across several workers and combine the results?
- How do you cancel a job that is already running?
Describe how you would handle database schema migrations for a live system with zero downtime.
Medium · System Design and Architecture
Approach
- Use expand and contract (parallel change). During a rolling deploy, old and new application versions run against the same schema at once, so every schema step must work with both.
- Expand: add the new column or table as nullable. In PostgreSQL that is a metadata-only change, and since version 11 adding a column with a non-volatile default no longer rewrites the table. Deploy code that writes both old and new, backfill, switch reads, stop writing the old column, and drop it in a later release (contract).
- Backfill in small batches by primary-key range so transactions stay short, row locks are brief and replication lag stays bounded. Make the backfill resumable and idempotent.
- Avoid long blocking locks: build indexes with CREATE INDEX CONCURRENTLY (it cannot run inside a transaction block). For NOT NULL, add a CHECK constraint NOT VALID, then VALIDATE CONSTRAINT, then SET NOT NULL, which PostgreSQL 12+ can do without a full scan. Set lock_timeout so an ALTER waiting behind a long transaction fails fast instead of queueing all traffic behind it.
- Never rename or retype a column in place: add the new one and migrate. Keep each step reversible, and verify between steps (row counts, null counts) before you contract.
Follow-up
- How would you rename a column that three services read?
- The ALTER TABLE is stuck waiting on a lock behind a long-running query: what do users experience, and what do you do?
- The backfill of hundreds of millions of rows is causing replica lag: how do you throttle it?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
Design a class hierarchy for a rule-based pricing engine.
Medium · Object-Oriented Programming and Live Cod
Approach
- Pin down the rules first: are they multiplicative factors, additive surcharges, caps and floors, or eligibility checks? The answer decides the interface. A typical shape is an abstract Rule with applies(context) -> bool and apply(premium, context) -> premium.
- Keep the engine a composition, not a deep inheritance tree. A PricingEngine holds an ordered list of Rule objects (Strategy plus chain). A new rule is a new subclass, and the engine never changes (open/closed).
- Model the input as a frozen dataclass of risk attributes and money as decimal.Decimal, never float. Each rule returns a new value and does not mutate shared state, so rules can be tested one at a time.
- Order matters: a 10% factor applied before or after a fixed surcharge gives different premiums. Make the order explicit (a priority field or the list position) and apply caps and floors last.
- Have each rule append a line (rule name, input, output) to a trace, so every premium can be explained. Cover the edge cases: no rule applies, two rules conflict, a required attribute is missing, the result goes negative.
Follow-up
- How would business users add or change rules through configuration without a code deploy?
- How do you version rule sets so a quote issued last year can be reproduced exactly?
- How would you unit-test rule ordering and the interaction between a multiplier and a cap?
How would you architect a scalable API to serve pricing data to external clients?
Medium · System Design and Architecture
Approach
- Clarify the workload: are clients fetching published rate tables (read-heavy, rarely changing) or requesting a quote computed per request? What are the volume, the latency target, and the number of client organisations?
- Edge: an API gateway handling authentication (API keys or OAuth2 client credentials), per-client rate limiting (a token bucket, returning 429 with Retry-After), and versioned endpoints such as /v1/. Behind it, stateless app servers on a load balancer that scale horizontally.
- Tenant isolation: take the client identity from the credential, never from a request parameter, and filter every query by it so one insurer can never read another's data.
- Published pricing versions are immutable, so cache them hard (CDN or Redis keyed by version, with ETag and If-None-Match). Computed quotes are deterministic for a given model version and input, so identical requests can be cached or deduplicated.
- For bulk needs, offer a batch endpoint or async job instead of millions of single calls. Make changes additive with a deprecation window, and log which model version priced each response so any quote can be audited and reproduced.
Follow-up
- A client needs to price a million policies overnight: do they use the same endpoint?
- How do you roll out a new pricing model version without breaking existing clients?
- What do you monitor per client, and what alert would wake someone up?
Design a frontend-to-backend data flow for a dashboard displaying real-time actuarial analytics.
Medium · System Design and Architecture
Approach
- Clarify what real-time means here (sub-second, or within a minute), how many concurrent viewers there are, how large the underlying data is, and whether figures are recomputed on new data or on user input.
- Backend: precompute aggregates (materialized views, an analytics store or a results table written by the computation jobs) so the API serves summarized, chart-ready data rather than raw rows. Downsample time series to roughly the resolution the chart can draw.
- Transport: polling is simplest. Server-Sent Events give one-way push over HTTP, and the browser reconnects automatically, sending Last-Event-ID so the server can resume. WebSockets are worth it only if the client also streams to the server. Send deltas or versioned snapshots.
- Fan-out: many viewers of the same view should share one computation. Compute once, publish through pub/sub, and push to all subscribers instead of running one query per viewer. Enforce tenant and permission filtering on the server.
- Frontend: one client-side store, throttled re-renders, virtualized tables for long lists, an "as of" timestamp on every panel, and a single snapshot version per page so charts never mix data from different recomputations.
Follow-up
- Thousands of users open the same dashboard at once: how do you avoid thousands of identical queries?
- A client's connection drops for thirty seconds: how does it catch up without a full reload?
- A recalculation takes several minutes: what does the dashboard show in the meantime?
I need a function to calculate a premium based on user risk. What clarifying questions do you have before writ
Medium · Domain Logic and Analytical Thinking
I need a function to calculate a premium based on user risk. What clarifying questions do you have before writing the code?
Approach
- Inputs: is "user risk" a score that is already computed, or raw attributes (age, vehicle, location, claims history)? What are the types, units and valid ranges, and what happens when a value is missing or out of range: reject, default factor, or route to manual review?
- Formula: base rate times multiplicative factors (the structure a log-link GLM produces, common in insurance pricing), additive loadings, or both? Where do the factor tables come from, who maintains them, and how are they versioned and tied to an effective date?
- Output: which currency, what rounding rule (Python's round() rounds half to even, so name the rule and use Decimal.quantize with an explicit mode), minimum and maximum premium, and whether taxes, fees or discounts are included.
- After the questions, state the assumptions aloud and write a pure, testable signature, for example premium(attributes, rate_table, as_of_date) -> PremiumResult, that returns the amount and a per-factor breakdown so the result can be explained.
- Name the edge cases you will test: unknown category, boundary ages, zero or negative results, a missing rate table for the date, and changes to factor precision.
Follow-up
- The rating table has no entry for a category in the input: what does your function do?
- How would you change the design to support several currencies or several rate tables in parallel?
- How would you prove a premium was calculated with the rules in force on the policy date?
Explain your understanding of how AI can be applied to insurance pricing transparently.
Medium · Domain Logic and Analytical Thinking
Approach
- Set up the tension: classic insurance pricing relies on generalized linear models, whose coefficients per rating variable an actuary can read, justify and adjust. Black-box models such as gradient-boosted trees or neural networks can fit better, but each premium is hard to explain to an actuary or a regulator.
- Name two routes to transparency. Either use machine learning to automate building an interpretable model (variable selection and smoothing with regularization: L1 or lasso drives weak coefficients to exactly zero, giving sparse, readable models), or explain a black box after the fact with SHAP values or partial dependence, which are approximations and can mislead when features are correlated.
- Define transparent concretely: every premium decomposes into contributions per rating factor, a person can inspect and override each factor's curve, and the same inputs and model version always reproduce the same price.
- Add the risks: protected attributes and their proxies (for example location standing in for a protected trait), data drift after deployment, and the trade-off between accuracy and interpretability.
- Close with the engineering side: version every model, log which version priced each quote, and keep the inputs needed to reproduce and audit any premium.
Follow-up
- Why not train a gradient-boosted model and explain it with SHAP?
- How does lasso regularization help with variable selection, and what is the effect of the regularization strength?
- How would you detect that a model uses a proxy for a protected attribute?
Look at this short Python snippet involving a mathematical calculation. What is it doing, and how can it be op
Medium · Domain Logic and Analytical Thinking
Look at this short Python snippet involving a mathematical calculation. What is it doing, and how can it be optimized?
Approach
- First say what the code computes: trace it on a tiny input and name the result (a weighted average, a variance, a compound growth series, a binomial coefficient). Then state its complexity.
- Look for repeated work: sum() or len() recomputed inside a loop (O(n^2) that becomes O(n) once hoisted), membership tests against a list (O(n) each, against O(1) average for a set), repeated list or string concatenation, recursion that recomputes subproblems (functools.lru_cache fixes it).
- Vectorize with NumPy: np.dot, np.cumsum, broadcasting and boolean masks move the loop into compiled code. Point out the cost in large temporary arrays, and note that fixed-width NumPy integer arrays can overflow and wrap silently, unlike Python ints.
- Check numerical correctness as well as speed: the one-pass variance E[x^2] - E[x]^2 suffers catastrophic cancellation (use a two-pass formula or Welford), math.fsum sums floats with correct rounding, and comparing floats with == should be math.isclose or np.allclose.
- Measure before and after with timeit or cProfile, and confirm the optimized output matches the original within a stated tolerance.
Follow-up
- The vectorized version runs out of memory on the full dataset: what do you do?
- How do you prove your faster version returns the same results as the original?
- When would you reach for Numba or Cython instead of NumPy vectorization?
Every query on one table stalls for forty seconds mid-deploy
Medium · lock contention · ddl · migrations
During a release on PostgreSQL, every query touching resource times out for about 40 seconds and then recovers with no intervention. The release ran one migration, ALTER TABLE resource ADD COLUMN archived_reason TEXT, and the migration log shows it completing in 6 ms. Unrelated tables showed no change in error rate. Explain how a 6 ms statement caused a 40-second stall, give the ordered checks you would run on a live system to confirm it, and give the migration procedure that prevents a repeat.
Approach
- Separate the statement's duration from the lock's duration. ADD COLUMN with no default is a catalogue-only change and genuinely runs in milliseconds, but it requires ACCESS EXCLUSIVE, and it cannot acquire that until every transaction already touching the table has finished.
- Account for the queueing, which is the part that surprises people. A lock request that is waiting blocks later requests for conflicting modes behind it rather than letting them overtake, so one long-open transaction holds the DDL and the DDL holds all the traffic. The stall length is set by the longest open transaction, not by the size of the change.
- Confirm on a live system in this order: pg_stat_activity for that table ordered by xact_start, looking for the oldest transaction and specifically for state = idle in transaction; then pg_locks where granted = false to find the waiter; then join them on pid to name blocker and blocked. pg_blocking_pids() does that join for you and is the fastest single call.
- Prevent rather than merely time it better. Set lock_timeout to a second or two on the migration session so the DDL abandons the queue after a bounded wait and is retried, instead of holding it for as long as the oldest transaction lives. Be exact about what that buys: queries arriving during the wait still queue behind the pending ACCESS EXCLUSIVE request, so each attempt costs them up to one lock_timeout of added latency. The outage goes from 40 seconds to about one second per attempt, not to zero. Also run migrations away from deploy-time peaks, and put a statement timeout and an idle-in-transaction timeout on the analytics role that opens the long transactions.
Follow-up
- The same release also wants NOT NULL on that column. What is the sequence that gets there without a long lock?
- Your lock_timeout retry fails ten times in a row because the analytics transaction is always open. What do you change?
Suggested practice checklist
Each day targets one stage of the reported loop and the questions that belong to it, moving from setup and algorithms to design and finally the leadership conversations.
Day 1 — Map the loop and set up your machine
- Write the five reported stages in order, one line each, and mark that system design depends on seniority.
- Create a fresh virtual environment with pytest, NumPy and Pandas, open a blank project, and run a test screen share.
- Write and time a two-minute resume pitch that explains why a pricing platform for insurers interests you.
Deliverable: A one-page loop map, a working environment checklist ticked after the test screen share, and a written two-minute pitch.
Day 2 — Online assessment: algorithms and multiple choice
- Solve three timed problems (sliding window, hashing, sorting) on a CodinGame-style platform and note the complexity of each.
- Implement time-series anomaly detection: flag points beyond a threshold using a rolling mean and standard deviation, then a median and MAD variant.
- Test it on an empty series, a window longer than the series, a constant series with zero variance and missing values.
Deliverable: Three timed solutions with complexity notes, plus one anomaly detector with tests for the listed edge cases.
Day 3 — Live coding: object-oriented design and refactoring
- Build a rule-based pricing engine: a rule interface, concrete rules, an ordered pipeline, and pytest tests, narrating choices aloud.
- Write a parser that validates an insurance-style JSON payload, returns every error rather than the first, and processes valid records.
- Refactor a messy 60-line script of your own to SOLID, with characterisation tests written before the first change.
Deliverable: A runnable pricing-engine module with tests, a validator with a test file, and a refactor with before and after tests.
Day 4 — Ambiguity and numerical Python
- For the premium function, write clarifying questions grouped by inputs, missing data, rounding, output and audit, then list assumptions and code a first version.
- Take three numerical snippets (accumulating in a Python loop, repeated list or string concatenation such as lst = lst + [x], recomputing inside a loop), explain each aloud and rewrite with NumPy.
- Time before and after with timeit and write one sentence on why the second version is faster, for example because the Python loop is now vectorised.
Deliverable: A one-page question and assumption list for the premium function, and three snippets each with an explanation and timing comparison.
Day 5 — System design: asynchronous models, pricing API, migrations
- Design the asynchronous model-run system: submit, queue, workers, status endpoint, result store, retries, timeouts and idempotency keys.
- Design the pricing API for external clients: authentication, versioning, rate limits, caching, pagination and error format.
Deliverable: Two design diagrams with failure notes, a numbered migration plan, and written answers to the two practice items.
Day 6 — Dashboard data flow and the AI pricing explanation
- Design the real-time actuarial dashboard flow: choose polling, server-sent events or WebSockets, add pre-aggregation and caching, and say what is allowed to be stale.
- Prepare a two-minute spoken explanation of how AI can price insurance transparently and record yourself giving it.
- Write a one-page glossary: premium, frequency, severity, loss ratio, rating factor, explainability.
Deliverable: A dashboard data-flow diagram with the staleness decision stated, a recorded two-minute explanation, and a one-page glossary.
Day 7 — Leadership rounds and a full mock
- Write the vague-requirement story and one project walkthrough, each ending with a business outcome, and time both under three minutes.
- Have a friend interrupt and challenge your answers while you give them, and practise restating and adjusting.
Deliverable: Two written stories, one recorded mock session, and written answers to the two practice items.
Stories for the leadership and culture rounds
Candidates report that the final rounds mix motivation with technical territory, often with senior stakeholders, so stories need to show architecture decisions and their business impact. The role also involves working with data scientists, actuaries and product managers, so rehearse stories about collaboration with non-engineers and about acting on incomplete information.
- Walk me through a past project, focusing on the architectural decisions and the business impact.
- Tell me about a time you worked with a data scientist or another non-engineer to put a model or analysis into production.
- Describe a time you were given incomplete information and had to decide between starting and stopping to ask. What did you ask?
- Tell me about a time someone challenged your technical decision bluntly. What did you change and what did you defend?
Tell me about a time you had to translate a vague business requirement into a concrete technical architecture.
Medium · Domain Logic and Analytical Thinking
Approach
- Pick a story in which you owned the translation from a request phrased as an outcome (for example, pricing changes need to be reviewable by non-engineers) to a design. Open with the situation in two sentences and save the rest of the time for your reasoning.
- Show how you reduced the ambiguity: the questions you asked, who answered, which assumptions you wrote down and had confirmed, and which you chose to leave open. Candidates report that Akur8 prompts are often deliberately vague, so showing this habit is useful practice.
- Trace requirement to decision explicitly: each requirement should produce a component or constraint, such as results needed within minutes leading to an asynchronous worker queue. Name one alternative you rejected and why, in terms of cost, complexity or reversibility.
- Close with the outcome in business terms (a metric you measured, a stakeholder who could now do something they could not before) and one thing that changed later. Candidates are advised to stress business impact with senior stakeholders.
- Avoid two failures: a story where the architecture was already dictated to you, and a list of technologies that is not tied to any requirement. Say I for your own decisions and name who else contributed.
Follow-up
- Which requirement turned out to be wrong or changed afterwards, and what did it cost to adapt?
- How did you decide you knew enough to start building instead of asking more questions?
- How did you explain the design to a non-engineer, and where did they push back?
Turn a code review disagreement into a decision
Easy · code review · optimistic concurrency · communication
A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.
Approach
- Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
- Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
- Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
- Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.
Follow-up
- Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
- The author says clients cannot handle a 409. How do you check whether that is true?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?