Interview guides · AXA XL
AXA XL Software Engineer Interview Guide
AXA XL Software Engineer interview guide: four reported rounds, Python coding, math puzzles, system design and behavioral questions, plus a 7-day plan.
The 60-second brief
AXA XL is described by candidates as a commercial insurance and reinsurance provider. Software engineers there are reported to build APIs, backend services and data integration pipelines that turn actuarial and mathematical models into running software, including cloud-hosted pricing platforms and real-time data processing. The people using those tools are underwriters and risk analysts, and the role involves close work with product owners, data scientists, actuaries and business stakeholders spread across Europe, North America and Asia-Pacific. For you, that means the code you write is judged partly by whether the numbers it produces can be trusted.
This guide is for candidates preparing for a Software Engineer interview at AXA XL. It covers the four reported rounds, the sixteen questions candidates report (Python coding, math and statistics, system design and project architecture, and behavioral collaboration), and a seven-day plan. The requirements candidates describe list Python, data structures and algorithms, building and consuming APIs and relational databases as must-haves, with Azure, Pandas or NumPy and insurance experience as pluses. Reports mention 2+ years of experience, and system design depth is described as expected at mid-to-senior levels.
Candidates report four rounds over roughly three to five weeks: an Initial Screening, a Technical Evaluation, Panel Interviews and an HR and Leadership Interview. Format and timing vary by team and region.
Focus areas
- Python
- Problem Solving
- Statistics
Overview
The work described for this role sits between modelling and production. Engineers build APIs, backend services and data pipelines, and they take models written by data scientists and actuaries and make them run reliably inside a larger software estate. That is why the reported questions pair ordinary engineering topics (bracket validation, recursive traversal, memory use in Python, batch file ingestion, API hardening, schema migrations) with analytical ones (trailing zeros in 100!, what repeated square roots converge to, mean versus median, a probability calculation in plain Python).
Python is the language to be sharpest in. The listed requirements put Python first, followed by data structures and algorithms, API work and SQL, with Azure, Pandas and NumPy as pluses. Practise in the standard library: generators, collections.Counter, heapq, itertools, math.comb and the statistics module are the tools that let you answer the memory and probability questions without leaning on scientific packages. Know your complexity vocabulary well enough to show it on a real function, not just define it.
Plan for two kinds of preparation that do not overlap much. The first is coding and design practice that you can rehearse aloud. The second is derivation: the math and statistics questions reward working from first principles (count the factors of 5, take logarithms of the repeated square root) rather than recalling a number. Add stories about cross-region work, disagreements over requirements, vague tasks and mistakes, because the reported behavioral questions and the final HR and leadership stage lean on them.
Use the rounds as a pacing device. Early stages are about your background and a take-home or live coding task, so tidy fundamentals matter first. The panel stage combines coding, design, logic puzzles and discussion of past projects, so the second half of your preparation should be mixed practice, with one project you can describe down to its bottleneck and the fix.
The AXA XL interview process
Candidates report four rounds over roughly three to five weeks, with some accounts stretching to six. The order and exact format can shift with the team and region.
Initial Screening
Candidates describe the Initial Screening as a conversation with a talent acquisition specialist or a hiring manager about your background, career goals and fit with the role. It is the first place the listed requirements come up: Python, data structures, APIs and SQL. Treat it as the round where your story has to be tidy, because every later stage builds on what you say here.
What to demonstrate
- Your background and career goals as you describe them
- How your experience lines up with the listed requirements: Python, APIs, SQL, data structures
- Clarity when summarising your work to a non-specialist
How to prepare
- Write a short walkthrough of your career that ends with why a role building APIs, services and data pipelines for insurance-related models fits you.
- Pick one project where you can name the stack, the bottleneck and the fix, and rehearse it; it will be reused for the architecture question later.
- List what you want to ask: which team and region, whether the Technical Evaluation is a take-home or live, and how the later rounds are organised.
Technical Evaluation
Candidates report the Technical Evaluation as either a take-home coding challenge or a live technical assessment of your coding skills. The reported coding questions are the natural practice for it: validating a string of brackets, a recursive traversal of a nested structure, cutting the memory use of a Python script, and explaining time versus space complexity with code. Ask the recruiter which format you will get and prepare for both.
What to demonstrate
- Working, readable Python on algorithmic problems
- Choice of data structure and awareness of time and space complexity
- For a take-home, structure, tests and documentation that let someone else run your work
How to prepare
- Write the bracket validator with a stack and a closing-to-opening dictionary, then test an empty string, a lone closer, interleaved types like "([)]" and a leftover opener.
- Write the nested traversal recursively, then iteratively with an explicit stack, and be ready to explain Python's default recursion limit and when it matters.
- For a take-home, practise delivering a small project with a README that states assumptions and run commands, unit tests with edge cases, and a clear module layout.
- For a live task, rehearse talking through input assumptions, brute force, improvement and final complexity while you type.
Panel Interviews
Candidates describe Panel Interviews as deeper discussions covering coding, system design, logic puzzles and your past projects. The reported question types that fit this format include the math and statistics puzzles (trailing zeros in 100!, repeated square roots, mean versus median, a probability calculation without scientific libraries), the system design questions (batch file ingestion, API security and rate limiting, schema migrations) and the project architecture walkthrough. Prepare to move between all of them in one sitting.
What to demonstrate
- Coding ability and system design skill, as the round is described
- Problem-solving on logic and probability puzzles
- How you explain decisions, bottlenecks and trade-offs from your own projects
How to prepare
- Derive each reported math answer on paper until you can explain it without notes: 100! has 20 + 4 = 24 trailing zeros, and repeated square roots approach 1 for any positive start.
- Outline a batch-upload service end to end: storage, queue, chunked workers, idempotency, validation errors, status endpoint.
- Prepare a project walkthrough with architecture, one measured bottleneck, the change you made and the trade-off you accepted.
- Be able to discuss cloud choices on Azure or AWS, microservices versus a monolith, and the cost and latency trade-offs.
HR and Leadership Interview
Candidates report the HR and Leadership Interview as the closing conversation about team fit, compensation and long-term career growth. The reported behavioral prompts, such as cross-timezone work, disagreements with a product manager and learning from a mistake, are good rehearsal material for it. Go in with a clear view of your own expectations.
What to demonstrate
- Team fit and working style, particularly across regions
- Your career direction and what you want from the role
- How you discuss compensation and expectations
How to prepare
- Rehearse the four reported behavioral prompts with a different project behind each one.
- Decide in advance what you will say about compensation and growth, and what you need to ask about the team's structure.
- Prepare one example of explaining a technical concept to a non-technical stakeholder, since the role lists that as a requirement.
Where candidates lose points
Starting to code the bracket validator or nested traversal before pinning down the input
Ask which bracket types appear, whether other characters are allowed, whether the empty string counts as valid, and how deep the nesting can get. Python's default recursion limit is about 1000 frames, so say you would switch to an explicit stack for very deep input. Then test an interleaved case like "([)]" and a leftover opener out loud.
Answering the out-of-memory Python question with 'use a bigger machine' or a tool name
Give a sequence: measure first (tracemalloc or a memory profiler), find which structure holds the data, then stream with generators, read files in chunks (for example the chunksize option in pandas), choose smaller dtypes, and discard references you no longer need. Mention parallelism or external storage only after the single-process footprint is fixed.
Giving a bare number for the 100! or repeated-square-root puzzle without the reasoning
Show the derivation. Trailing zeros come from pairs of 2 and 5, and fives are scarcer, so count floor(100/5) + floor(100/25) = 24. For the calculator, each press halves the logarithm of the number, so the value tends to 1 whether you start above or below it. Check your result with a quick Python run after you explain it.
Describing a project as a list of components with no bottleneck, number or trade-off
Prepare one project with the problem, the measurement that exposed the bottleneck, the change you made and what it cost (complexity, money, consistency). Keep a version that fits the batch-upload design question: how the data arrives, where it is validated, how it recovers from a failed file.
Treating the take-home as a quick script and submitting without tests or a README
Reports warn that a messy submission can end your candidacy early. Add a README with assumptions and run instructions, unit tests that include the edge cases, readable module structure and type hints where they help. Re-run everything from a clean environment before sending it.
Questions & practice
Write a function to check if a string of brackets is valid and balanced (the classic Valid Parenthesis problem
Medium · Algorithms & Data Structures
Write a function to check if a string of brackets is valid and balanced (the classic Valid Parenthesis problem).
Approach
- Scan left to right with a stack. Push every opening bracket. On a closing bracket, pop and check it against a dict that maps each closer to its opener, e.g. {')': '(', ']': '[', '}': '{'}. A mismatch, or a pop from an empty stack, means the string is invalid.
- After the scan, the string is valid only if the stack is empty. Leftover openers such as '(()' are the case people most often forget to check.
- Cost: O(n) time, because each character is pushed and popped at most once, and O(n) extra space in the worst case (all openers).
- Edge cases: the empty string is valid, an odd length can be rejected straight away, and a string that starts with a closer fails on the first character. Ask whether non-bracket characters can appear, and skip them if they can.
- If there is only one bracket type, a single integer counter replaces the stack (O(1) space). It must never go negative and must end at zero.
Follow-up
- How would you return the minimum number of brackets to remove so the string becomes valid?
- How would you find the length of the longest valid substring, for example in ')()())'?
- How does the solution change if the input is a stream too large to hold in memory?
Implement a recursive solution to solve a nested data structure traversal problem.
Medium · Algorithms & Data Structures
Approach
- Pin the shape first, e.g. arbitrarily nested lists, or dicts of lists as in parsed JSON. Then state the base case (a leaf value) and the recursive case (a container: recurse into each child).
- In Python, check containers with isinstance(x, (list, dict)) and not with 'is it iterable'. A str is iterable, and each of its characters is itself a str, so treating strings as containers recurses forever.
- Cost: O(total number of elements) time, plus O(depth) call-stack space. Writing the flatten as a generator with 'yield from' keeps output memory lazy as well.
- CPython's default recursion limit is 1000 (sys.getrecursionlimit()). For very deep input, convert to an explicit stack of iterators rather than raising the limit.
- If the structure can contain itself, track id() of the containers you have visited, or the traversal never terminates.
Follow-up
- Rewrite it iteratively with an explicit stack and keep the original element order.
- How would you compute a depth-weighted sum, where each value is multiplied by its nesting depth?
- What happens if a list contains a reference to itself, and how do you detect it?
How would you optimize a Python script that is running out of memory when processing large datasets?
Medium · Algorithms & Data Structures
Approach
- Measure before changing anything. Use tracemalloc snapshots (snapshot.compare_to) or memory_profiler to find the lines that allocate, and check whether memory grows steadily (a leak or an accumulator) or spikes at load time (reading everything at once).
- Stream instead of loading: iterate the file object line by line, use csv.reader or a generator pipeline, and aggregate as you go. Avoid list(...) and readlines(), which keep the whole dataset alive.
- In pandas, use read_csv(chunksize=...) and combine per-chunk aggregates, load only the columns you need (usecols), and set dtypes explicitly: category for repeated strings, smaller int and float widths where the value range allows.
- For millions of small objects, use tuples or classes with slots instead of per-instance dicts. Drop references to large intermediates once they are no longer needed; CPython frees an object when its refcount hits zero, though the process may not return that memory to the OS.
- If the work cannot be chunked, such as a global sort or a join, push it to a database or a columnar file format with column pruning, or hash-partition the data to disk and process one partition at a time.
Follow-up
- How would you compute a group-by over data where even the set of distinct keys does not fit in memory?
- Memory grows slowly over hours in a long-running job. How do you find what is holding the references?
- Why can multiprocessing make memory use worse, and how would you avoid that?
Explain the difference between time complexity and space complexity with a concrete coding example.
Medium · Algorithms & Data Structures
Approach
- Define both terms as growth rates in the input size n. Time complexity counts operations; space complexity counts extra memory beyond the input (auxiliary space). Say which one you mean, because some people include the input.
- Use pair-sum as the example. Nested loops take O(n^2) time and O(1) extra space. One pass with a set of values seen so far takes O(n) average time and O(n) space. That is a trade of memory for time.
- Recursion stack counts as space. Naive recursive Fibonacci takes exponential time (about O(1.618^n)) and O(n) stack depth. Memoised, it takes O(n) time and O(n) space. Iterative with two variables, it takes O(n) time and O(1) space.
- Point out the Python costs that hide inside one line: 'x in list' is O(n) while 'x in set' is O(1) on average, and slicing copies, so s[1:] inside a loop or a recursion adds O(n) time and space per call.
- Say when you would pick the slower option, for example when memory is the binding constraint or n is small enough that constants dominate.
Follow-up
- What is amortised O(1), and why is list.append amortised O(1) when it sometimes copies the whole list?
- What is the worst-case lookup cost of a hash set, and when could it happen?
- What is the space complexity of merge sort compared with in-place quicksort?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Stop tag and share joins from fanning out a page
Medium · join fan-out · semi-joins · many-to-many
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
What is the total number of trailing zeros in 100 factorial (100!)? Explain your mathematical logic.
Medium · Mathematics, Probability & Statistics
Approach
- A trailing zero needs a factor of 10 = 2 x 5. There are far more factors of 2 than of 5 in 100!, so the answer is the number of factors of 5.
- Count them with Legendre's formula: floor(100/5) + floor(100/25) + floor(100/125) = 20 + 4 + 0 = 24. The 25 term exists because 25, 50, 75 and 100 each contribute a second 5.
- State it in code: count = 0; while n: n //= 5; count += n. That runs in O(log_5 n) and never computes the factorial.
- Check the edge cases: 0! = 1 and 4! = 24 have no trailing zeros, and 25! has 6 zeros, not 5.
Follow-up
- How many trailing zeros does 1000! have? (200 + 40 + 8 + 1 = 249.)
- Find the smallest n whose factorial has exactly k trailing zeros. Why does no factorial have exactly 5? (Binary search on n, since the count is monotonic; it jumps from 4 at 24! to 6 at 25!.)
- How would you count trailing zeros of n! in base 12, where 12 = 2^2 x 3?
If you repeatedly press the square root key on a calculator for any positive number, what value does it eventu
Medium · Mathematics, Probability & Statistics
If you repeatedly press the square root key on a calculator for any positive number, what value does it eventually approach? Why?
Approach
- It approaches 1 for every positive starting value. After k presses the value is x^(1/2^k), and the exponent 1/2^k goes to 0, so the value goes to x^0 = 1.
- Taking logs makes it rigorous: ln(x_k) = ln(x) / 2^k, which tends to 0 for any fixed x > 0.
- Fixed points: sqrt(x) = x gives x = 0 or x = 1. Since f'(x) = 1/(2*sqrt(x)) is 1/2 at x = 1, below 1 in magnitude, 1 attracts. Near 0 the derivative is unbounded, so 0 repels every positive start.
- Describe the direction. Values above 1 decrease monotonically toward 1, values in (0, 1) increase monotonically toward 1, and 1 itself stays put.
- On a real calculator or with floating point, the display shows exactly 1 after a finite number of presses, because sqrt(1 + e) is about 1 + e/2 and soon falls below the rounding precision.
Follow-up
- What happens if you press the square key repeatedly instead? (It diverges above 1, goes to 0 below 1, and stays fixed at 1.)
- About how many presses until the value is within d of 1? (Roughly log2(|ln x| / d).)
- Repeatedly pressing cos in radians converges to about 0.739. Why does that iteration converge?
Explain the difference between mean, median, and mode, and describe a scenario where the median is a more reli
Medium · Mathematics, Probability & Statistics
Explain the difference between mean, median, and mode, and describe a scenario where the median is a more reliable metric than the mean.
Approach
- Define each one. The mean is the sum divided by the count. The median is the middle value of the sorted data (the average of the two middle values when n is even). The mode is the most frequent value, which may not be unique and is the only one of the three that works for categorical data.
- Robustness: one extreme value can move the mean arbitrarily far, while the median can only be broken by corrupting half the data. In right-skewed data the mean sits above the median.
- Use a skewed example, such as household income, house prices, or individual insurance claim amounts, where a few very large values pull the mean far above what a typical case looks like. The median describes the typical case better there.
- Also say when the mean is the right choice: when you need a total, since the total equals the mean times n (for example, the expected aggregate cost across many cases). The median loses that relationship.
- Computation: the mean is one O(n) pass; the median is O(n log n) by sorting or O(n) on average with quickselect. In Python, statistics.mode returns the first mode it finds (3.8+), and statistics.multimode returns all of them.
Follow-up
- How would you maintain the median of a stream of numbers? (Two heaps, O(log n) per insert, O(1) to read.)
- What is a trimmed mean, and when would you use it instead of either the mean or the median?
- For a symmetric distribution the mean and median match. What does it tell you when they differ a lot?
How would you implement a basic probability distribution calculation in Python without relying on external sci
Medium · Mathematics, Probability & Statistics
How would you implement a basic probability distribution calculation in Python without relying on external scientific libraries?
Approach
- Ask which distribution and what operation (PMF, CDF or sampling), then use only the standard library. For a binomial: math.comb(n, k) * pk * (1 - p)(n - k), with the CDF as a running sum of the PMF.
- For large n, keep it numerically stable. Either work in log space with math.lgamma and exponentiate at the end, or use the recurrence P(k+1) = P(k) * (n - k) / (k + 1) * p / (1 - p), which avoids huge factorials.
- A normal CDF needs no external library: Phi(z) = 0.5 * (1 + math.erf(z / math.sqrt(2))). For an empirical distribution from data, use collections.Counter counts divided by n.
- To sample, use inverse-transform sampling. Build the cumulative probabilities once, draw u = random.random(), and find the bucket with bisect.bisect_right, which is O(log k) per draw.
- Validate the result: the PMF must sum to 1 within tolerance, k outside 0..n gives 0, p = 0 and p = 1 are degenerate (and the recurrence divides by 1 - p), and spot-check against values you know, such as the probability of exactly 5 heads in 10 fair flips, 252/1024.
Follow-up
- How would you estimate a probability by Monte Carlo, and how does the error shrink as you add samples? (Standard error falls like 1/sqrt(N).)
- How would you compute a Poisson PMF for a large lambda without overflow?
- How would you test that your sampler actually produces the target distribution?
Walk me through the architecture of a major project you worked on recently. What were the bottlenecks, and how
Medium · System Design & Project Architecture
Walk me through the architecture of a major project you worked on recently. What were the bottlenecks, and how did you resolve them?
Approach
- Open with context and scale in numbers: what the system did, who used it, request or data volumes, and what you personally owned compared with the team.
- Walk through the components in the order a request or record travels: clients, API layer, services, queues, data stores, external dependencies. Name the reason for each technology choice in one clause.
- Pick one or two real bottlenecks and show the evidence: a profile, p95 latency, a query plan, queue lag, or a memory graph. Being vague about how you found the problem weakens the whole story.
- Give the fix and its trade-off, for example an index that sped up reads but slowed writes, or a cache that added staleness. Then give the measured result, before and after.
- Close with what you would change today and what would break first at ten times the load. That shows you understand the design's limits.
Follow-up
- Why did you choose that database or queue over the alternatives you considered?
- What would fail first if the traffic or data volume grew tenfold?
- Which part of this did you build yourself, and which part did you inherit?
How would you design a backend service that needs to process large, batch-uploaded data files from clients?
Medium · System Design & Project Architecture
Approach
- Clarify the scale and the contract first: file sizes and formats, files per day, whether clients need synchronous feedback or a job status, and what happens to bad rows.
- Ingest: have clients upload straight to object storage through a short-lived pre-signed or SAS URL, so large files never pass through API servers. Record the upload as a job with a checksum.
- Processing: an upload event puts a message on a queue. Stateless workers stream-parse the file in chunks, validate each row against a schema, write rejects to an error report, and bulk-load valid rows (COPY or batched inserts) into a staging table before an atomic merge.
- Reliability: make jobs idempotent, for example a key of client plus checksum so a re-upload is detected. Checkpoint progress so a crashed worker resumes instead of duplicating rows, retry with backoff, and send poison files to a dead-letter queue.
- Expose a status API with an explicit state machine (uploaded, validating, processing, completed, failed, partially failed). Cover the security basics: size limits, malware scanning, encryption at rest, and per-client isolation.
Follow-up
- The same file is uploaded twice by mistake. What happens in your design?
- Five percent of rows fail validation. Do you reject the whole file or load the rest, and who decides?
- A single 50 GB file arrives. How do you parallelise it without breaking row order or the record boundaries in the file?
What strategies do you use to ensure your APIs are secure, rate-limited, and highly available?
Medium · System Design & Project Architecture
Approach
- Security: TLS everywhere, and authentication with OAuth 2.0 / OpenID Connect tokens. Validate the JWT signature, expiry, issuer and audience on every request. Use least-privilege scopes, schema validation of input, parameterised queries, and keep secrets in a secrets manager, never in code.
- Rate limiting: a token bucket per API key or client, enforced at the gateway and backed by a shared store (e.g. Redis) so every instance sees the same counts. Return 429 with a Retry-After header. A token bucket allows short bursts; a sliding window gives a smoother cap.
- Availability: stateless instances behind a load balancer across several availability zones, health checks that remove bad instances, and timeouts on every outbound call.
- Failure handling: retry only idempotent calls, with exponential backoff and jitter. Use idempotency keys so POST retries are safe, circuit breakers on flaky dependencies, and degraded responses instead of total failure.
- Observability ties it together: per-client request rates, error rates and p95/p99 latency, alerts on SLO burn, and audit logs for authentication failures.
Follow-up
- How do you revoke access for a compromised token before its JWT expiry?
- How do you enforce one global rate limit across several regions without a cross-region round trip on every request?
- Why can blind retries turn a partial outage into a full one?
How do you manage database migrations and schema changes in a continuous deployment environment?
Medium · System Design & Project Architecture
Approach
- Keep migrations versioned in the repo and applied by a tool such as Alembic, Flyway or Liquibase, as a pipeline step separate from the app rollout. During a rolling deploy, old and new app versions run against the same schema, so every migration must work with both.
- Use expand and contract. Add the new column or table in a backward-compatible way (nullable, or with a default), deploy code that writes to both, backfill in small batches, switch reads, and drop the old structure only in a later release.
- Watch locks. In PostgreSQL, build indexes with CREATE INDEX CONCURRENTLY (it cannot run inside a transaction block), set a lock_timeout so a blocked ALTER fails fast instead of queueing every query behind it, and remember that changing a column's type rewrites the table.
- Prefer rolling forward to down-migrations. Dropping a column cannot be undone, so separate the destructive steps and take a backup or snapshot first.
- Rehearse each migration on a production-sized copy to measure its duration and locking. In MySQL, DDL commits implicitly, so a multi-statement migration can fail halfway; PostgreSQL DDL is transactional.
Follow-up
- Walk me through renaming a heavily used column with zero downtime.
- How would you backfill a new column on a table with hundreds of millions of rows without hurting production traffic?
- A migration fails halfway through a deploy. What state is the database in, and what do you do next?
Exports duplicate a row range about once a week
Hard · leases · clock skew · idempotency
Roughly once a week an export writes a file containing a duplicated range of rows. The affected job_run rows show attempt = 1, status = succeeded, one started_at, and a lease_owner naming a different host from the one whose logs show the job starting. Leases last 30 seconds and are heartbeated every 10 from inside the handler; lease_expires_at is computed on the worker and compared against the database's now(). Find the mechanism, and give a fix that holds even if you cannot fix the clocks.
Approach
- Start from the fact that eliminates the obvious answer. attempt = 1 means no retry was recorded, so this is not a re-run after failure; two workers ran the same row concurrently and the takeover path never touched the counter. lease_owner naming a host other than the one that started the job is the same statement from the other side.
- Enumerate the mechanisms that cause a premature takeover, then find the signal that separates them. Either the lease genuinely expired because the heartbeat did not fire, which is what happens when the heartbeat runs on the handler's own thread and the handler makes a long blocking call, or it only appeared expired because two clocks disagree, since lease_expires_at is written from the worker's clock and evaluated against the database's. The discriminator is the distribution: incidents clustered on the longest exports indict the heartbeat, incidents clustered on one host indict skew. Measure both, and measure each host's offset against the database directly.
- Read the reclaim query precisely. In PostgreSQL now() is transaction start time, not statement time, so a reclaimer holding a long transaction compares against an older timestamp than expected; clock_timestamp() is the statement-time function. This is worth ruling in or out before you redesign anything, because it changes which rows look expired.
- Remove the second clock rather than trying to synchronise it. Issue and extend the lease in the database, with lease_expires_at = now() + interval '30 seconds' in both the claim and the heartbeat, so exactly one clock is ever compared and worker skew stops mattering to this predicate.
Follow-up
- The displaced worker has already streamed half the file to object storage. What makes that side effect safe to repeat?
- You now count takeovers. What alert fires on that counter, and at what threshold?
Suggested practice checklist
The plan follows the four reported rounds and the sixteen reported questions: coding first, then Python memory, math, design, SQL and database work, and finally behavioral stories and a mixed run-through.
Day 1 — Map the loop and rate yourself on the listed requirements
- Write the four rounds in order and note which are conversations, which are coding and which mix coding, design, puzzles and projects.
- List the requirements candidates describe (Python, data structures, APIs, SQL, plus Azure and Pandas or NumPy as pluses) and rate yourself on each.
- Choose one past project to walk through in full: architecture, the bottleneck, the fix, the trade-off.
Deliverable: A one-page loop map, a self-rated requirements list and a named project with its bottleneck.
Day 2 — Core coding: stacks, recursion and complexity
- Write the bracket validator with a stack and test an empty string, a lone closer, interleaved types and a leftover opener.
- Write a recursive traversal of nested dicts and lists, then rewrite it iteratively with an explicit stack and note the recursion limit.
- Explain time versus space complexity using both functions, and refactor one O(N^2) pair-search into an O(N) hash-set version.
Deliverable: Two tested functions (recursive and iterative) plus a written time and space note for each.
Day 3 — Python memory and large-data handling
- Write a script that reads a large generated CSV into a list, and measure its peak memory with tracemalloc.
- Rewrite it with a generator and chunked reading, then try smaller dtypes, and record the new peak.
- Outline a spoken answer: measure, locate, stream, shrink, only then parallelise.
Deliverable: A before-and-after memory measurement for one script and a written spoken-answer outline.
Day 4 — Math puzzles and statistics in plain Python
- Derive the trailing zeros of 100! by hand (floor(100/5) + floor(100/25)) and confirm with math.factorial.
- Explain why repeated square roots approach 1 from any positive start, including starts below 1.
- Write mean, median and mode without the statistics module and build an example (values with one huge outlier) where the median is steadier.
- Write a probability distribution from the standard library (a binomial pmf with math.comb, or a Counter-based empirical distribution) and check it sums to 1.
Deliverable: A worked derivation sheet and a standard-library script with assertions for the distribution and averages.
Day 5 — System design and project architecture
- Outline the batch-upload service: upload to storage, queue, chunked workers, idempotent processing per file, row-level error report, status endpoint.
- Write answers for API security (authentication, validation, per-client rate limiting such as a token bucket), availability (health checks, timeouts, retries with idempotency keys) and name Azure or AWS services you would use.
- Write an expand-and-contract migration plan: add nullable column, backfill, dual write, switch reads, remove old column later.
- Rehearse the project walkthrough from day 1 aloud.
Deliverable: Three design outlines (batch upload, API hardening, migration) and one rehearsed project walkthrough.
Day 6 — SQL and database depth
- Write the SQL for your migration plan from day 5 for one real table change and note what locks it takes.
- Do a short Pandas drill: groupby, merge and a vectorised calculation on a small dataset.
Deliverable: Two solved SQL practice problems with notes on concurrency and fan-out, plus the migration SQL.
Day 7 — Behavioral stories and a full mixed run
- Write outlines for the four reported behavioral prompts, each from a different project, with a result and a lesson.
- Run a mixed mock: one coding question, one math puzzle, one design question and one story, all aloud.
- Write your questions for the HR and leadership stage: team structure, growth, working across regions, and your compensation expectations.
Deliverable: Four story outlines, one recorded mock run with notes, and a list of questions for the final stage.
Behavioural preparation
Candidates report behavioral questions about working with teams in other timezones or regions, disagreeing with product managers or stakeholders over technical requirements, handling vague requirements and learning from technical mistakes. The role is described as collaborative with data scientists, actuaries and underwriters, so prepare stories that show your own decisions, the evidence you used and what changed afterwards.
- Describe a time you explained a technical concept to a non-technical stakeholder.
- Tell me about a code review where you helped a more junior engineer improve.
Tell me about a time you had to work with a team in a different timezone or region. How did you ensure effecti
Medium · Behavioral & Team Collaboration
Tell me about a time you had to work with a team in a different timezone or region. How did you ensure effective communication?
Approach
- Choose a story with named regions or timezones and a concrete problem it caused, such as a review waiting a full day or a requirement misread across a handoff. A story where nothing went wrong shows nothing.
- Describe the mechanisms you set up and why: a daily written handoff note, an agreed overlap window for decisions that need talking, design notes and pull request descriptions that stand alone, a visible decision log with owners.
- Say which of those you chose yourself, not what the team did in general. Replace 'we used Slack and calls' with what you wrote, scheduled or changed.
- Close with evidence it worked: fewer blocked reviews, faster turnaround, a release that landed without a clarification loop. Admit what you would adjust.
Follow-up
- What do you do when a blocking decision needs the other region and they are offline?
- Tell me about a handoff that went wrong and how you repaired it.
- How do you adapt your communication style for a team in a different culture or language?
Describe a situation where you had a disagreement with a product manager or stakeholder regarding a technical
Medium · Behavioral & Team Collaboration
Describe a situation where you had a disagreement with a product manager or stakeholder regarding a technical requirement. How did you resolve it?
Approach
- Pick a technical requirement you disagreed on, such as scope, an unrealistic deadline or a shortcut that risked data accuracy, and describe the other person's goal fairly before yours.
- Show how you moved the conversation from opinion to evidence: a small prototype, an estimate, a measured latency figure, a worked example of a wrong output, or a list of options with cost and risk.
- Explain how you translated the technical issue into business terms the stakeholder could weigh, since the role involves explaining technical ideas to non-technical colleagues.
- State the outcome honestly. Compromise is fine, and so is conceding the point and learning later it was right. A story where the other person was simply wrong tends to sink the answer.
Follow-up
- What if they had still disagreed after you showed the evidence?
- How did you keep the relationship working afterwards?
- Did the decision turn out to be right, and how did you find out?
How do you handle a situation where you are given a task with vague or incomplete requirements?
Medium · Behavioral & Team Collaboration
Approach
- Show a repeatable method, not a one-off: restate the goal and the user, list your assumptions, and rank your questions by what it would cost to be wrong, asking the expensive ones first.
- Give a concrete case. For example, for a data feed with an unspecified file format, ask for sample files, field meanings, error handling, volume and who consumes the output.
- Describe how you reduced risk while waiting for answers: a thin slice that works end to end, behind a flag, with the assumptions written down in the ticket or pull request.
- Show you confirmed the interpretation early with the requester, and what changed after the first demo.
Follow-up
- What if the stakeholder is unavailable and you must start anyway?
- How do you decide you have enough information to begin?
- Tell me about a time an assumption you wrote down turned out to be wrong.
Tell me about a technical mistake you made in a past project and what you learned from it.
Medium · Behavioral & Team Collaboration
Approach
- Choose a real mistake where you owned the decision, with consequences that were visible: a bad migration, a missing validation, a misjudged edge case in a pipeline. A disguised strength such as 'I worked too hard' does not answer the question.
- Tell it in order: what you did, how it was discovered, what it affected and who you told. Own it without blaming reviewers, requirements or teammates.
- Spend most of the answer on the fix and prevention: the rollback or patch, then the test, alert, review checklist or runbook that now catches the same class of error.
- Finish with how you act differently now, with a recent example where that change paid off.
Follow-up
- How did you find out, and how quickly did you tell stakeholders?
- What would you check today before shipping the same kind of change?
- Did anyone else on the team change a habit because of it?