cswork / libraryOpen practice workspace

Interview guides · Anistar

Anistar Software Engineer Interview Guide

Anistar Software Engineer interview guide: two reported rounds, code efficiency and refactoring questions, and a seven-day prep plan.

The 60-second brief

Candidates describe Anistar as a company whose software engineers build and maintain software that connects underlying infrastructure with what end users need. Reports do not name a specific product, customer or technology stack, so this guide does not guess at one. What they do describe is the shape of the job: you design, build and maintain software, take a task from first idea through deployment, tune existing codebases for efficiency, and add new features. The day-to-day also includes sprint planning, code reviews, some technical mentoring, and working with other engineers, project managers and operational teams to turn business requirements into working code.

This guide is for candidates interviewing for the Software Engineer role at Anistar.

Candidates report two rounds over roughly 2-4 weeks: Initial Screening with the recruiting team, then Technical Evaluations that go deeper on problem-solving.

Focus areas

  • Code Efficiency
  • Performance Optimization
  • Algorithmic Complexity (Time/Space)

Overview

The reported material for this loop is consistent. Every reported question is about the quality of code once it exists: how you keep a large codebase efficient, how you refactor legacy code to make it faster, which metrics tell you an implementation worked, and how you weigh fast deployment against code quality. Candidates also describe topic areas of code efficiency, performance optimization, algorithmic complexity (time and space), problem solving and engineering practices. Preparation therefore centres on measuring, explaining and defending decisions, more than on memorising puzzle solutions.

Because the questions are open-ended, the strongest preparation is to build a small set of concrete stories and mental checklists that you can adapt. For efficiency, that means a repeatable sequence: define the workload, measure, find the dominant cost, change one thing, measure again. For refactoring, it means how you protect behavior before touching old code. For metrics and release speed, it means naming specific signals and specific safeguards instead of general principles. Reports also list system architecture and code review standards as areas to be ready to discuss, so have a view on how you design for scale and how you give and receive review comments.

The requirements candidates describe are core programming languages, data structures and algorithms, and version control, with cloud infrastructure, automated testing frameworks and CI/CD pipelines as extras. No language is named, so pick the one you are most fluent in and be ready to state the time and space cost of what you write. The recurring advice is to explain the reasoning behind each choice, so practise saying why you picked an approach and what you gave up, out loud, because the same habit serves the screening call and the technical evaluations.

The Anistar interview process

Candidates report two stages. The reports do not say which of the four reported questions belongs to which stage, so prepare all of them for the technical evaluations.

Initial Screening

Candidates describe Initial Screening as a first connection with the recruiting team to check basic qualifications. Reports give no format beyond that, so treat it as a conversation about your background against the role. The requirements candidates describe are core programming languages, data structures and algorithms, and version control, with cloud infrastructure, automated testing and CI/CD as nice-to-haves. Be ready to state plainly where your experience matches each of those and where it does not, and to describe one or two past projects in enough detail that the technical stage can build on them.

What to demonstrate

  • Basic qualifications for the Software Engineer role, as the stage is described
  • How your experience lines up with the listed requirements: core languages, data structures and algorithms, version control
  • How clearly you summarise past projects and your own part in them

How to prepare

  • Write a short summary of two projects covering the problem, your contribution, the language you used and one measurable outcome, such as latency, memory or run time before and after.
  • List which of the nice-to-have areas you have touched (cloud infrastructure, automated tests, CI/CD pipelines) with one sentence of concrete evidence for each.
  • Pick the language you will use in technical stages and refresh its standard collections so you can quote the cost of common operations.

Technical Evaluations

Candidates describe Technical Evaluations as the deeper stage where you demonstrate how you solve problems. Reported topic areas are code efficiency, performance optimization, algorithmic complexity (time and space), problem solving and engineering practices, and reports advise being ready to discuss performance tuning, system architecture and code review standards. Reports also advise starting with the high-level approach, asking clarifying questions on ambiguous problems and being candid about trade-offs.

What to demonstrate

  • Problem-solving: how you break an ambiguous problem into steps and handle edge cases
  • Code efficiency and algorithmic complexity in time and space
  • Performance optimization: how you find and fix bottlenecks
  • Engineering practices such as refactoring, review standards and balancing speed with quality

How to prepare

  • Rehearse an answer about managing code efficiency in a large project: define the workload, measure, find the dominant cost, change one thing, measure again.
  • Prepare one real bottleneck story with before-and-after numbers, and one legacy refactoring story that explains how you protected existing behavior.
  • Write a short list of metrics you would use to judge an implementation (latency percentiles, error rate, resource use, change failure rate) and explain which you would rank first for a given goal.
  • Practise the coding drills about rolling failure rates and top-heavy tenants, stating time and space complexity before you write code.

Where candidates lose points

Calling code efficient without a measurement behind it

For every efficiency claim, name the workload, the number before, the number after, and the tool or timing method that produced it. If you only reasoned about it, say so and describe how you would confirm it.

Describing a legacy refactor as a rewrite with no safety net

Open with how you pinned down existing behavior (tests around current outputs, a side-by-side comparison, a feature flag), then describe small steps that each ship and can be rolled back. A rewrite story has to explain how you knew nothing broke.

Answering the success-metrics question with a long unranked list

Choose two or three metrics tied to the goal of the implementation, say which one decides success, and name a guardrail metric that would show you caused harm elsewhere, such as error rate or memory use.

Treating speed versus quality as a slogan

Describe the mechanisms that let you ship quickly without lowering the bar: automated tests, code review, CI/CD checks, small changes behind flags, easy rollback. Then say when you would deliberately slow down and why.

Starting to code before clarifying and skipping complexity analysis

Restate the problem, ask about input size and edge cases, give a high-level approach, and state time and space cost before and after you write it. Name the brute-force version first so the improvement is visible.

Questions & practice

Track a rolling failure rate per destination for circuit decisions

Easy · sliding window · ring buffer · circuit breaker

The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.

Approach

  1. Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
  2. Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
  3. State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
  4. Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.

Follow-up

  • The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
  • A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?

Identify the heaviest tenants in a five-minute window under memory pressure

Medium · top-k · heavy hitters · streaming

The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.

Approach

  1. Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
  2. Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
  3. State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
  4. Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.

Follow-up

  • The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
  • Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?

Canonicalise a request body into a stable idempotency fingerprint

Medium · parsing · canonicalisation · hashing

idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.

Approach

  1. Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
  2. Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
  3. Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
  4. Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.

Follow-up

  • A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
  • Where does the fingerprint get computed relative to request decompression and the body-size limit?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.

Approach

  1. Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
  2. Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
  3. For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
  4. State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.

Follow-up

  • A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
  • How do you honour an erasure request while resource_revision.actor_user_id still references this table?

How do you approach managing code efficiency in a large-scale project?

Medium · Technical Proficiency and Code Efficienc

Approach

  1. Define efficiency as budgets before touching code: p95/p99 latency per critical path, throughput, memory and cost per request. Averages hide the tail, so I state percentiles and the workload they were measured under.
  2. Measure before optimising: a sampling profiler (py-spy, async-profiler, pprof) and flame graphs on production-like load to find the hot path. Amdahl's law sets priorities: making code that is 10% of runtime twice as fast speeds the whole thing up by only about 5%.
  3. Fix at the highest level first. Start with algorithmic complexity (a nested-loop O(n^2) match replaced by a hash map, O(n) expected), then I/O patterns (N+1 queries batched into one IN query or join, missing indexes confirmed with EXPLAIN), then caching with a defined invalidation rule, and micro-tuning last.
  4. Stop regressions at scale: benchmarks in CI with a tolerance threshold, a performance item in the code review checklist for hot paths, and production dashboards and alerts on p99 and saturation so a slow merge shows up within one deploy.
  5. Trade-off: optimised code is often harder to read, so I only optimise measured hotspots, leave a comment or benchmark explaining why the code is shaped that way, and keep the simple version everywhere else.

Follow-up

  • With hundreds of endpoints and limited time, how do you decide which one to optimise first?
  • Describe a case where adding a cache made correctness or performance worse. How would you handle invalidation?
  • How do you keep a performance benchmark in CI from producing flaky failures on shared runners?

Can you describe your process for refactoring legacy code to improve performance?

Medium · Technical Proficiency and Code Efficienc

Approach

  1. Baseline first: reproduce the slow path with production-sized data, record p50/p95/p99, CPU, memory and query counts, then profile so the refactor targets the measured bottleneck, not the code that looks oldest.
  2. Pin current behaviour before changing it: legacy code often has no tests, so I write characterization (golden-master) tests that capture today's outputs, including odd ones, and introduce seams such as an extracted function or injected dependency so the slow part can be tested alone.
  3. Change in small, reversible steps, one per commit, and rerun the benchmark after each. Typical wins: batching N+1 queries, swapping O(n) list membership checks for O(1) set lookups, moving loop-invariant work out of loops, and adding an index after reading the query plan.
  4. Roll out safely: put the new path behind a feature flag or a strangler-fig wrapper, run it in shadow mode comparing old and new outputs, canary it, and keep the old path until metrics match the baseline target.
  5. Edge cases that break 'pure' refactors: changed iteration order (a set or hash map does not preserve list order), null and empty inputs, floating-point summation order, and concurrency assumptions the old code relied on implicitly.

Follow-up

  • How do you refactor a module with no tests, no documentation and no original author available?
  • The faster version returns the same rows in a different order and a downstream caller breaks. What do you do?
  • How did you justify the refactoring time against feature work, and what number showed it paid off?

What metrics do you prioritize when evaluating the success of a software implementation?

Medium · Technical Proficiency and Code Efficienc

Approach

  1. Agree what success means before shipping: the user or business outcome the change targets (adoption, task completion, conversion, cost) plus guardrail metrics that must not get worse, each compared with a pre-launch baseline.
  2. Service health via the four golden signals: latency as p50/p95/p99, traffic, error rate and saturation (CPU, memory, queue depth, connection pool). I express targets as SLOs with an error budget so 'healthy' is a number, not a feeling.
  3. Delivery health via the four DORA metrics: deployment frequency, lead time for changes, change failure rate and time to restore service. These show whether the implementation is easy to change and safe to ship.
  4. Maintainability signals: defects escaping to production, incidents traced to the component, review turnaround and test coverage on changed code, treated as signals rather than targets because of Goodhart's law.
  5. Read the numbers carefully: averages hide tail latency, a before/after comparison needs the same traffic mix and season, and a metric that moves on its own needs a control group or an A/B test before I claim credit.

Follow-up

  • Latency improved after your release but conversion dropped. How do you work out whether the release caused it?
  • How would you pick an SLO target for a new service that has no history yet?
  • If you could keep only three of these metrics for a project, which three and why?

How do you balance the need for rapid deployment with the requirement for high code quality?

Medium · Technical Proficiency and Code Efficienc

Approach

  1. Reframe the trade-off: small, frequent changes are easier to review, test and roll back. The DORA research finds that throughput and stability tend to improve together rather than trading off, so I aim for small batches, not a choice between speed and quality.
  2. Automate the quality bar so it does not depend on willpower: CI running unit and integration tests, linters and type checks, required review, and a pipeline kept fast enough that nobody is tempted to skip it.
  3. Separate deploy from release: feature flags, canary or progressive rollout, and automatic rollback when error rate or latency crosses the SLO. That lets unfinished work ship dark without reaching users.
  4. Scale scrutiny with reversibility: a copy change or flagged UI tweak can move fast; schema migrations, public API contracts and data deletion get extra review and expand/contract migrations, because they cannot be undone by redeploying.
  5. When a deadline forces a shortcut, take it deliberately: record the debt with an owner and a revisit date, add a test around the risky part, and in the answer give a real example with what the shortcut cost or saved.

Follow-up

  • Tell me about a time you shipped something you knew was not ideal. What happened next, and did you pay the debt back?
  • Production needs a hotfix now and the full CI run is slow. What do you skip, if anything, and how do you limit the risk?
  • How do you release a database schema change that cannot be rolled back without blocking the team?

One customer endpoint stalls deliveries to every other destination

Medium · bulkhead · timeouts · saturation

The egress service delivers about 1.5k webhooks/second across 40,000 destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. Throughput falls to 300/second, queue depth climbs, and p99 delivery latency for unaffected destinations goes from 200 ms to minutes, while the error rate barely moves. One tenant holds 900 destination rows whose URLs share a hostname that now answers in 9.5 seconds. Explain the mechanism with the arithmetic, then give the containment in the order you would apply it.

Approach

  1. Look at saturation before errors. A flat error rate with collapsing throughput says nothing is failing, things are waiting, so the first signal to pull is in-flight request count or pool wait time rather than the error counter. This is the distinction that decides the whole investigation.
  2. Group in-flight work by resolved host, not by destination id. The cap is keyed per destination row, so 900 rows sharing one hostname buy 3,600 concurrent slots against a single host, each held for 9.5 seconds. The bulkhead was never a bulkhead for that host, and grouping by the wrong dimension is why the dashboard looked healthy.
  3. Do the arithmetic in both directions. Required concurrency is arrival rate times latency, so 1.5k/second at 200 ms needs about 300 in flight, which is entirely consumed by 3,600 slow slots; conversely whatever concurrency is left sustains rate equals concurrency divided by 9.5 seconds, which is the 300/second you are seeing. Matching both numbers is what promotes this from a plausible story to the mechanism.
  4. Explain why the circuit breaker never helped. It opens on consecutive failures, and a 9.5-second response inside a 10-second timeout is a success. Slow is not failing, so an error-rate breaker cannot see this; you need a slow-call ratio, a deadline propagated from the caller's remaining budget, or a concurrency limiter.

Follow-up

  • The host recovers to 80 ms. How long does the queue take to drain, and what does the drain do to the recovered host?
  • Where should the 10-second timeout number actually come from?

Suggested practice checklist

The plan follows the two reported stages and the four reported questions: Day 1 maps the loop, Days 2 to 5 cover complexity, efficiency, refactoring, metrics and release trade-offs, Day 6 rehearses stories, and Day 7 is a full dry run.

Day 1 — Map the two stages and inventory your projects

  • Write out Initial Screening and Technical Evaluations in a few lines each, noting what candidates report about each stage and what you are least ready for.
  • List four past projects and, for each, the language, how you used version control, what testing existed and any performance number you can state.
  • Mark which projects could carry a bottleneck story and which could carry a legacy refactoring story.

Deliverable: A one-page loop map plus a project inventory table with a performance number or a gap marked for each project.

Day 2 — Algorithmic complexity and efficient data structures

  • Write time and space costs for the structures you reach for most: array, hash map, heap, queue and balanced tree.
  • Solve the rolling failure-rate drill and say aloud which structure keeps each update cheap and what happens as the window grows.
  • Solve the heaviest-tenants drill and compare an exact approach with a memory-bounded one, stating the trade-off.

Deliverable: A complexity cheat sheet and two solved drills, each with a spoken time and space analysis written beneath it.

Day 3 — Efficiency in a large codebase and finding bottlenecks

  • Write your answer to managing code efficiency in a large-scale project as a five-step sequence: workload, measurement, dominant cost, single change, re-measurement.
  • Write your bottleneck story with before-and-after numbers and the tool or timing method you used.
  • Work through the debugging drill where one endpoint stalls other destinations and list the first three things you would check.

Deliverable: A written efficiency answer, a bottleneck story with numbers, and a three-step diagnosis list for the stalled-deliveries case.

Day 4 — Refactoring legacy code for performance

  • Write your refactoring process as ordered steps: pin existing behavior, profile, pick the smallest change, ship behind a flag or in small commits, compare results, retire the old path.
  • Work through the drill on denormalising tenant onto revisions and backfilling it live, noting how it avoids downtime and how you would verify the backfill.
  • Prepare one real legacy refactor from your work, including what you chose not to change and why.

Deliverable: A step-by-step refactoring answer, notes on the live backfill drill, and one refactor story from your own experience.

Day 5 — Success metrics and balancing speed with quality

  • Build a table of five metrics (for example latency percentiles, error rate, resource use, change failure rate, time to restore) and write when each one is the primary measure of success.
  • Write your answer on balancing rapid deployment with code quality, naming the specific checks and rollback mechanisms you rely on.
  • Write one example where you shipped quickly and one where you slowed down on purpose, with the reasoning for each.

Deliverable: A ranked metric table plus a written answer on speed versus quality, backed by two real examples.

Day 6 — Collaboration and communication stories

  • Rehearse your bottleneck story aloud, then retell it for a non-technical teammate in plain language.
  • Work through the drill on unblocking an engineer without taking the keyboard, which maps to code review and mentoring.
  • Work through the drill on narrating an outage from page to postmortem and tighten it to the numbers that matter.

Deliverable: A recorded or written plain-language version of your bottleneck story and outlines for the mentoring and outage drills.

Day 7 — Full dry run

  • Solve the idempotency fingerprint drill aloud, stating assumptions, complexity and edge cases as you go.
  • Answer all four reported questions out loud in sequence, each with a method, an example and a trade-off.
  • Write three questions to ask the recruiting team about the next stage, and list the weakest answer to revisit.

Deliverable: A completed spoken run of the four reported questions, one solved coding drill with stated complexity, and three questions to ask.

Stories about performance and trade-offs

Reported behavioral material for this role is about performance work and engineering judgment: finding and fixing a bottleneck, keeping code efficient as users and data grow, refactoring old code, and weighing release speed against quality. Reports also describe daily collaboration with engineers, project managers and operations teams, plus code reviews, so prepare stories that show the decision you made, the evidence you used and the trade-off you accepted. Include numbers wherever you can.

  1. Walk me through a time you identified a performance bottleneck and the steps you took to resolve it.
  2. How do you ensure your code remains efficient as the user base or data volume scales?
  3. Describe a legacy refactor you led: how you protected existing behavior, what you changed first and how you measured the result.
  4. Tell me about a time you chose to ship quickly and a time you slowed down for quality, and what decided each.
  5. Describe a code review where you disagreed with a teammate on an approach and how it was settled.
  6. Explain a technical decision to a non-technical stakeholder and what you changed after their feedback.

Unblock an engineer without taking the keyboard

Easy · mentoring · leases · at-least-once

A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.

Approach

  1. Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
  2. Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
  3. Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
  4. Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.

Follow-up

  • How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
  • Their handler calls an external endpoint before recording that it did. What do you tell them to change first?

Narrate an outage you owned from page to postmortem

Hard · incident response · blast radius · postmortems

Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.

Approach

  1. Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
  2. Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
  3. Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
  4. Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.

Follow-up

  • What would you do differently in the first five minutes, given the same dashboard and no more information?
  • Which follow-up action did you deliberately not take, and why was dropping it the right call?

Tell callers you do not own that their integration breaks

Medium · deprecation · compatibility · stakeholders

A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.

Approach

  1. Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
  2. Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
  3. Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
  4. Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.

Follow-up

  • How would you detect a consumer that reads the field only during a monthly export?
  • One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?