cswork / libraryOpen practice workspace

Interview guides · Athelas

Athelas Software Engineer Interview Guide

Athelas Software Engineer interview guide: four reported rounds, Python API scripting, full-stack builds, system design and a 7-day prep plan.

The 60-second brief

Athelas builds healthcare automation software for medical billing and revenue cycle management, and candidates also mention AI-driven clinical integrations. The product areas named for this role are RCM Core, Biller Workflows and Claims Submissions, with AI Integrations listed among the work as well. A Software Engineer builds full-stack features, integrates third-party APIs and scales the backend that handles claims and other administrative data. The stack candidates see listed is Python, React and GCP, and SQL is named as a skill that is tested.

This guide is for candidates interviewing for a Software Engineer role at Athelas. It walks through the four reported rounds, how to prepare for the Python API-parsing, full-stack build and backend-scaling questions candidates report, the behavioral prompts, and a seven-day plan. The behavioral questions get worked answer structures here; the technical question cards carry their own guidance.

Candidates report four rounds over roughly 3-5 weeks: Recruiter Screen, Conversational Interview, Technical Screen and a Virtual Onsite made up of several sessions.

Focus areas

  • Python
  • API Integration
  • Data Parsing & Transformation

Overview

The work described for this role is practical: shipping full-stack features, integrating third-party APIs, and growing a backend that handles claims data. Preparation should match. Candidates describe the reported questions as hands-on rather than puzzle-style: call an API and reshape its JSON with Python, stand up a small CRUD application with a React frontend, and talk through how to scale a backend component. Spend your hours writing and running code, not memorising obscure data structures.

Several reported entries are phrased as topic summaries rather than prompts, for example the ones about Python and API integration, about spinning up web apps quickly, and about architectures that handle growth. Treat each as a skill area to rehearse. The concrete prompts candidates report include querying a public API such as a chess API and modifying the parsed results, writing a script from provided API docs, building a CRUD app, connecting a React UI to a backend service, scaling a backend component under heavier data load, and deciding between refactoring a monolith and splitting services. Reported scenarios also mention scaling a service for high volumes of claims submissions and ingesting webhook events reliably, so be ready to talk about retries, duplicate delivery and idempotent writes.

The process includes conversations as well as coding. Candidates describe a Conversational Interview about past projects and behavioral scenarios, and a final alignment call with engineering leadership inside the Virtual Onsite. A take-home project is reported in some loops. Prepare four or five stories about ambiguity, ownership and cross-functional delivery, and be able to explain each one to someone outside engineering.

The Athelas interview process

Candidates report four rounds over roughly 3-5 weeks. The order and contents below are what candidates describe, and teams or seniority levels can adjust them.

Recruiter Screen

Candidates describe an opening conversation about your background, your location preferences and how you fit the role. No coding is reported for this stage. Use it to give a clear, short account of your path, point to work that resembles API integration or full-stack feature delivery, and explain why healthcare billing automation interests you. Ask what the remaining rounds contain so you can plan your preparation around them.

What to demonstrate

  • How clearly you summarise your background and past projects
  • How your experience maps to Python, React, full-stack work and API integration
  • Your location preferences and logistics

How to prepare

  • Write a short career summary that ends on the work most similar to API integrations or full-stack features.
  • Prepare one honest sentence on why healthcare billing automation, based on something you actually know about it.
  • List your location and start-date constraints, and ask the recruiter to confirm the remaining rounds and whether a take-home is part of the loop.

Conversational Interview

Candidates describe a deeper conversation with a hiring manager or a member of the sales engineering team, centred on past projects and behavioral scenarios. This is where the stories about ambiguity, ownership and cross-functional delivery are used. Choose projects you built end to end, know the stack and your own decisions in detail, and be ready to explain them to a listener who is not a backend specialist.

What to demonstrate

  • How well you explain past projects, including what you personally decided and built
  • Behavioral scenarios such as ambiguous requirements and cross-functional delivery
  • Communication with a mix of technical and non-technical colleagues

How to prepare

  • Prepare two project walkthroughs: problem, your role, stack, one hard decision and the outcome.
  • Rehearse the stories for unclear instructions and for a cross-functional feature under a tight deadline, aloud.
  • Practise a plain-language version of one technical decision, for example why you stored data the way you did.

Technical Screen

Candidates describe a focused technical stage where you query an API and parse its data with Python. Reported examples include querying a public API such as a chess API, modifying the parsed JSON as instructed, and reading provided API docs to write a script that meets specific requirements. Read the docs completely before coding, print the raw response first, then build the parser step by step while saying what you assume.

What to demonstrate

  • Working Python that calls an API and transforms the returned JSON
  • Reading an unfamiliar API spec and implementing its requirements accurately
  • Handling missing fields, odd values and failed requests in the responses

How to prepare

  • Write scripts against two public JSON APIs using requests, with timeouts, status checks and pagination.
  • Practise defensive parsing: dict.get with defaults, type checks, and a clear decision on whether to skip or fail on a bad record.
  • Add retry with backoff for 429 and 5xx responses; candidates list rate limits and retry logic as less common extras.

Virtual Onsite

Candidates describe the Virtual Onsite as several sessions: a full-stack application build, a system design discussion, and a final alignment call with engineering leadership. The build rewards a running vertical slice, so get create and read working end to end before polishing. In the design session, name the bottleneck before the component, and in the leadership call bring concrete ownership stories.

What to demonstrate

  • A working CRUD application connecting a React frontend to a backend and database
  • System design reasoning: data modeling, scaling a backend component and failure points
  • Ownership and long-term fit in the conversation with engineering leadership

How to prepare

  • Build a small CRUD app from an empty folder with a Python backend, a SQL table and a React form and list, and get it running end to end.
  • Practise designing a claims-submission service and a webhook ingestion pipeline, covering idempotency, queues and retries.
  • Prepare an answer on when to refactor a monolith versus split a service, with a concrete trigger such as team or deploy contention.

Where candidates lose points

Parsing an API response as if every record had the happy-path shape

Check the status code, set a timeout, follow pagination to the last page, and use safe access for optional fields. Decide aloud what happens to a record that fails parsing, then test that case with a hand-made bad record.

Coding the API script before reading all of the provided API docs

Read the spec through first and list authentication, parameters, pagination and rate-limit rules. Print one raw response before writing the transformation, so the output you build matches what the prompt asks for.

Spending the full-stack build on structure and extras, so nothing runs at the end

Get one create and one list working from the React form through the backend to the database first. Add update, delete and validation after that, and keep a short list of what you would add next if given more time.

Design answers that list components without naming the bottleneck, for example adding a queue to a claims-submission service with no reason

Say which path is slow or at risk: reads, writes, a third-party call or the database. Then choose the fix, and cover duplicate delivery and retries with idempotent writes for webhook events and submissions.

Behavioral stories told entirely as 'we', with no personal decision and no result

For each story, write down the one decision that was yours, the alternative you rejected, and a result you can state. Practise the story aloud until the setup takes only a few sentences.

Questions & practice

Collapse a redelivered event batch into per-aggregate high-water marks

Easy · hashing · at-least-once · aggregation

You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.

Approach

  1. One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
  2. Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
  3. Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
  4. If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.

Follow-up

  • The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
  • How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?

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?

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?

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?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.

Approach

  1. Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
  2. Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
  3. Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
  4. Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.

Follow-up

  • The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
  • Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?

Tests your proficiency in writing clean scripts, handling API requests, and parsing unstructured data payloads

Medium · Python and API Integration

Tests your proficiency in writing clean scripts, handling API requests, and parsing unstructured data payloads.

Approach

  1. Split the script into fetch, parse, transform and output functions so each step can be tested alone, and drive it from a small main() that takes its inputs as arguments.
  2. Fetch through a requests.Session with an explicit timeout (requests waits indefinitely by default) and call raise_for_status(); response.json() raises on a non-JSON body, so catch that and log the first part of the raw text.
  3. Parse defensively: use dict.get with defaults, check isinstance before treating a value as a list or dict, normalise key case and whitespace, and use re only for genuinely free-text fields.
  4. Follow pagination until the cursor or next link is empty, retry only GETs on 429, 5xx or connection errors with capped exponential backoff, and honour a Retry-After header when present.
  5. Count and log skipped records with a reason instead of crashing on the first bad one; the pass is O(n) over records, and stream=True with iter_lines keeps memory bounded on large NDJSON responses.

Follow-up

  • How would you test this without calling the live API (unittest.mock or the responses library with recorded fixtures)?
  • What do you do when the API returns HTTP 200 with an error object in the body?
  • How would you speed it up for thousands of requests without breaking the rate limit?

Query a public API (such as a chess API) to fetch data, parse the JSON results, and perform required data modi

Medium · Python and API Integration

Query a public API (such as a chess API) to fetch data, parse the JSON results, and perform required data modifications.

Approach

  1. Read the endpoint docs, then print one raw response and inspect its keys before writing any transform; for example the Chess.com public API lists a player's monthly game archives as URLs, while Lichess exports games as NDJSON when asked for application/x-ndjson.
  2. Fetch sequentially with a Session, a timeout and raise_for_status, and cache each response in a dict or local file so reruns during the session do not hit the API again.
  3. Flatten each game into a record: find the player's colour by comparing the lowercased username with the white and black usernames, then pull rating, result, time control and end time, converting epoch seconds with datetime.fromtimestamp(ts, tz=timezone.utc).
  4. Map result codes explicitly (Chess.com uses codes such as win, checkmated, resigned, timeout, agreed, stalemate, repetition) to win, loss or draw instead of assuming two values, then aggregate with Counter or defaultdict in O(n), sorting only when order matters (O(n log n)).
  5. Cover the edge cases out loud: unknown user (404), empty archive months, missing rating or result fields, username case mismatch, and 429 responses when requests run too fast.

Follow-up

  • How would you compute the player's longest winning streak from the archives?
  • What changes if the endpoint streams NDJSON instead of returning one JSON document?
  • How would you make this fast for a player with many years of archives?

Write a Python script to analyze provided documentation and execute specific API query requirements.

Medium · Python and API Integration

Approach

  1. Read the documentation once end to end and write a checklist: base URL, auth scheme (API key header, bearer token or OAuth client credentials), required parameters, pagination style, rate limits and error format; state each ambiguity and the assumption you are taking.
  2. Build a small client: a Session with the auth header set once, a get() wrapper that joins the base URL, sets a timeout, calls raise_for_status and retries 429 or 5xx with backoff.
  3. Write pagination as a generator that yields items until the cursor is null or the page is short, and guard against a repeated cursor so a server bug cannot loop forever.
  4. Map each requirement to one function, push filters into query parameters when the docs allow it to cut payload size, and check the output against a few records verified by hand.
  5. Read secrets from os.environ rather than hard-coding them, never log the token, and handle 401 separately from 403 since they mean different things (bad credentials versus missing permission).

Follow-up

  • The token expires halfway through a long run: what does your client do?
  • The documentation and the actual response disagree on a field: which do you trust and how do you handle it?
  • If the script also writes records through the API, how do you make a rerun safe?

Evaluates your ability to spin up functional web apps and build end-to-end features quickly.

Medium · Full-Stack and Web Application Developme

Approach

  1. Have a rehearsed scaffold you can start from memory, such as FastAPI or Flask with SQLite on the backend and a Vite React app on the frontend, so a running skeleton exists within minutes.
  2. Build one vertical slice first: one table, one endpoint, one screen wired end to end; widen to the remaining features only once that slice works in the browser.
  3. Work in the order data model, API, UI, validation and error states, then polish, and smoke-test each endpoint with curl before connecting the UI so failures are easy to locate.
  4. Solve cross-origin calls early with the Vite dev-server proxy or CORS middleware (FastAPI CORSMiddleware, flask-cors) rather than discovering it at the end.
  5. Say what you are deliberately skipping (auth, pagination, tests, deployment) and what you would add first with more time.

Follow-up

  • What would you add before this app could go to production?
  • How would you test the end-to-end flow quickly?
  • How would you deploy it, for example as a container on GCP Cloud Run?

Build a simple full-stack web application from scratch and get it up and running within a tight timeframe.

Medium · Full-Stack and Web Application Developme

Approach

  1. Pin down the user story and the minimum feature list, then write the schema and endpoint list before coding, for example items(id, name, status, created_at) with list, create, update and delete routes.
  2. Backend: FastAPI with Pydantic models gives request validation (422 on bad input) and interactive docs at /docs; return 201 on create and 404 for a missing id, and use sqlite3 or SQLAlchemy for storage.
  3. Frontend: fetch the list in useEffect, render a controlled form with useState, show loading, error and empty states, and update the list after each mutation by refetching or applying the change immutably.
  4. Keep the app runnable at every step: add a seed script, test endpoints with curl first, and commit each working state so a late mistake is cheap to undo.
  5. Name the trade-offs taken for speed, such as SQLite instead of Postgres or no auth, and how each would be replaced.

Follow-up

  • Add search and pagination to the list: what changes on each side?
  • How would you add login to this app?
  • Two users edit the same record at once: what happens and how would you handle it?

Implement basic CRUD operations using modern frameworks like React and backend technologies.

Medium · Full-Stack and Web Application Developme

Approach

  1. Map operations to HTTP: POST /items returns 201 with the created body, GET /items and GET /items/{id} read, PUT replaces and PATCH partially updates, DELETE returns 204; PUT and DELETE are idempotent, POST is not.
  2. On the backend, validate input at the boundary, use parameterised queries with bind parameters to prevent SQL injection, and back the rules with database constraints such as NOT NULL and UNIQUE.
  3. In React, keep the list in state, fetch in useEffect with an AbortController cleanup, use controlled inputs, and update state immutably after a mutation, for example setItems(prev => prev.filter(i => i.id !== id)).
  4. Handle the edge cases: deleting an item that is already gone (404 or treat as done), an empty list, field-level validation errors shown next to inputs, and double submits prevented by disabling the button while a request is pending.
  5. Mention TanStack Query as an option for caching, refetching and invalidating the list after mutations once the app grows.

Follow-up

  • How would you implement an optimistic update and roll it back if the server rejects it?
  • Soft delete or hard delete: which would you pick here and why?
  • How would you stop one user's edit silently overwriting another's (version column, or ETag with If-Match returning 412)?

Walk through your approach to connecting a frontend UI to a backend API service.

Medium · Full-Stack and Web Application Developme

Approach

  1. Agree the contract first: endpoints, JSON shapes, status codes and one error format, ideally as an OpenAPI spec from which frontend types can be generated.
  2. Wrap calls in one API client with the base URL from configuration (import.meta.env.VITE_* in Vite) and JSON headers; note that fetch resolves on 4xx and 5xx, so check response.ok and throw on failure.
  3. Explain CORS: a JSON POST or a custom header triggers a preflight OPTIONS request, the server must return the matching Access-Control-Allow-* headers, and a credentialed request cannot use a wildcard origin.
  4. Model every UI state (loading, error, empty, success), cancel stale requests with AbortController when a component unmounts or input changes, and debounce search-as-you-type.
  5. Choose where auth lives: a bearer token in an Authorization header or an httpOnly cookie sent with credentials: 'include', and state the XSS versus CSRF trade-off of each.

Follow-up

  • Where would you store the auth token, and what attack does each option expose you to?
  • The backend is slow or failing: what does the UI do, and which requests are safe to retry?
  • How do you keep frontend and backend types in sync as the API changes?

Examines how you structure architectures to handle growth and maintain performance.

Medium · System Design and Scaling

Approach

  1. Start from numbers: requests per second, read/write ratio, data growth and the latency target, and find the actual bottleneck from p95/p99 latency and slow-query logs before changing the design.
  2. Keep app servers stateless behind a load balancer so they scale horizontally, with sessions and shared state in a store such as Redis or the database.
  3. Scale the data layer in order of cost: indexes for hot queries, connection pooling, cache-aside with TTLs and invalidation on write, read replicas (accepting replication lag), and sharding only when one primary cannot take the writes.
  4. Move slow or bursty work (third-party calls, file processing) to a queue with workers; delivery is usually at least once, so consumers must be idempotent.
  5. Remove single points of failure and add timeouts, retries with backoff, circuit breakers and dashboards, so you can see which component degrades first.

Follow-up

  • Traffic grows tenfold: what breaks first, and how would you know before users do?
  • How do you keep the cache consistent with the database after writes?
  • If you had to shard, how would you pick the shard key and what query would it make expensive?

How would you approach scaling a specific backend component to handle increased data load?

Medium · System Design and Scaling

Approach

  1. Profile before redesigning: decide whether the component is CPU, database I/O, memory or downstream-API bound using metrics such as p99 latency, queue depth, database CPU and lock waits.
  2. Decouple intake from processing: validate the request, write it to a durable queue (Pub/Sub on GCP, Kafka or SQS), return 202, and autoscale workers on queue depth; an ingest path for claims or webhook events fits this shape.
  3. Make processing idempotent with a dedupe key such as the event id under a unique constraint, so redeliveries do not double-apply, and send repeated failures to a dead-letter queue.
  4. Relieve the database: batch inserts, index the hot lookups, partition append-heavy tables by time, archive old rows, and move reporting reads to a replica.
  5. Protect downstream services with rate limits, backpressure and bulkheads, and confirm the result with a load test against the target throughput.

Follow-up

  • How do you keep per-entity ordering when many workers consume in parallel (partition or ordering keys)?
  • A bug corrupted a day of processed events: how do you replay them safely?
  • How would you prove the scaled design handles the new load before shipping it?

Architect a basic CRUD application and explain your database and API design choices.

Medium · System Design and Scaling

Approach

  1. List the entities and relationships first, then justify a relational database such as Postgres for structured data, transactions and constraints: primary keys, foreign keys, NOT NULL, UNIQUE, created_at and updated_at.
  2. Index foreign keys and the columns used in filters and sorts, and explain each index by the query it serves and the write cost it adds.
  3. Design the API around resources: plural nouns, correct status codes, one error body format, filtering through query parameters, and cursor (keyset) pagination, since OFFSET reads and discards every skipped row on large tables.
  4. Layer the code as route, service and repository, validate at the boundary, and wrap multi-row writes in a transaction.
  5. Cover concurrency and history: optimistic locking with a version column, and soft delete with deleted_at plus a partial unique index if records must be kept for audit.

Follow-up

  • When would you choose a document store instead of a relational database here?
  • How would you keep a full change history for each record?
  • How would you add text search over these records?

How do you decide when to refactor monolithic code versus splitting services in a fast-paced product environme

Medium · System Design and Scaling

How do you decide when to refactor monolithic code versus splitting services in a fast-paced product environment?

Approach

  1. Default to a modular monolith with enforced module boundaries, and split only for a concrete pressure: a part needs independent scaling, a different deploy cadence or owner, stronger fault or security isolation, or a different runtime.
  2. Name what a split costs: network calls that fail, no cross-service transactions (sagas or the transactional outbox instead), distributed tracing, and extra deploy and on-call work.
  3. If the pain is code quality rather than scale, refactor inside the monolith first: clear interfaces, no reaching into another module's tables, tests at module boundaries; that also makes a later extraction cheap.
  4. When a split is justified, use the strangler fig pattern: put the module behind an interface, give it its own data, route a slice of traffic to the new service, compare results, then cut over.
  5. Check cheaper fixes first: a CPU-heavy job slowing the web tier can often move to a background worker process without becoming a separate service.

Follow-up

  • Two services need the same data: who owns it and how does the other read it?
  • How do you keep data consistent when one action updates state in two services?
  • What signs would tell you a service was split too early?

Edge instances grow 400 MB per hour until the nightly restart

Hard · memory leak · heap analysis · metric cardinality

Edge API instances start at 700 MB resident and grow about 400 MB/hour; a nightly rolling restart has hidden it for weeks. Growth continues unchanged when request rate halves overnight, p99 degrades in the last hours before an instance is recycled, and heap used immediately after a forced full GC rises monotonically. The service holds no product state. Name the discriminating measurement that separates the plausible causes, give the most likely cause, and give the fix and how you would verify it.

Approach

  1. Separate resident memory from live heap first, because they fail differently. Resident size can grow from fragmentation, native buffers or thread stacks while the heap is flat; heap used after a full GC rising monotonically is the measurement that says objects are reachable and not being released. You already have it, so this is retention, not fragmentation, and that closes off half the candidate list.
  2. Use the rate's independence from traffic as the discriminator. Growth that continues at half the request rate rules out per-request objects that are merely slow to collect and points at a structure that grows with distinct values observed rather than with call volume. Write the candidates that have that property: a metrics registry keyed on a high-cardinality label, an unevicted cache, an interner, a per-key lock map.
  3. Take two heap snapshots an hour apart and diff by retained size, reading the dominator tree, not by allocation count or instance count. Expect one root holding a map with millions of entries, then follow the reference chain to the code that inserts and never removes. Allocation profilers point at churn, which is the wrong signal here.
  4. The candidate that fits this service is an observability label carrying an identifier, such as a request path recorded before templating so that /v1/resources/48213 becomes its own metric series. That grows with distinct ids seen, is independent of rate, and explains the late p99 degradation, since GC cost rises with the size of the live set.

Follow-up

  • Post-GC heap is now flat but resident size still creeps. What are you looking at, and does it matter?
  • How would you have detected this before an OOM, given the nightly restart masked the trend?

Suggested practice checklist

The plan moves in the order of the reported loop: scripting against an API, reading docs, building a full-stack app, designing for growth, and then behavioral rehearsal. Each day produces something you can run or say aloud.

Day 1 — Map the loop and set up your toolkit

  • Write the four reported rounds (Recruiter Screen, Conversational Interview, Technical Screen, Virtual Onsite) on one page, with the question types each could contain and the one you are least ready for.
  • Create a Python project with a virtual environment, requests, pytest and a small helper that fetches JSON with a timeout and status check.

Deliverable: A one-page loop map with your weakest area marked, and a runnable Python helper that fetches JSON safely.

Day 2 — Fetch, parse and reshape a public API

  • Pick a public JSON API (a chess API works) and write a script that fetches records, follows pagination and extracts specific fields.
  • Apply a transformation such as grouping, filtering or renaming fields, and print the result in a clean structure.
  • Talk through the script aloud as if in the Technical Screen, stating your assumptions about the response shape.

Deliverable: One working script plus a short spoken walkthrough that you record or write out.

Day 3 — Docs-driven scripts and defensive parsing

  • Take an API you have never used, read its docs fully and write a requirements checklist before coding.
  • Write the script, then add retry with backoff for 429 and 5xx responses and handle null or missing fields.
  • Save three real responses as fixtures and write pytest tests for a missing field and for a malformed record.
  • Write your story for the question on hidden constraints in unfamiliar API responses, using the real incident from this exercise or a past project.

Deliverable: A requirements checklist, a tested script with retry logic, and a short written story for the API-edge-case question.

Day 4 — Build a CRUD app from an empty folder

  • Create a backend in Python with a SQL table, and implement create and list endpoints first, with input validation.
  • Add a React page with a form and a list that calls those endpoints, then add update and delete.
  • Write the setup steps so the app starts from a clean checkout, and note which choices you made quickly and what you would revisit.

Deliverable: A running full-stack CRUD application and a README with the start-up steps.

Day 5 — Wire the frontend to the backend and justify the design

  • Walk through how a React form submission reaches the database: request shape, validation, error responses, CORS and where credentials live.
  • Write down your schema and endpoint list for the CRUD app and the reason for each choice: keys, indexes, status codes and pagination.
  • Practise explaining the connection and the design choices aloud in one pass.

Deliverable: A schema and endpoint table with a reason for each choice, and a spoken walkthrough of the request path.

Day 6 — Scaling and service-boundary discussion

  • Design a claims-submission service for much higher volume: identify the slow path, then choose caching, queues, indexing or partitioning for it.
  • Design a webhook ingestion pipeline with duplicate delivery and retries, using idempotent writes.

Deliverable: Two one-page design sketches (claims submission and webhook ingestion) and a written trigger list for splitting a service.

Day 7 — Behavioral stories and a full dry run

  • Write five stories (background, ambiguity, cross-functional deadline, communication, a project you owned) with your decision and the result in each.
  • Run them aloud, then do a full dry run: one API script, then a short design question, then two stories.
  • Note the two weakest answers and rewrite them.

Deliverable: Five written stories and a recorded dry run with two answers rewritten.

Behavioral stories for the Athelas loop

Candidates report behavioral content in the Conversational Interview and in the alignment call with engineering leadership. For this role the prompts centre on your past projects, handling unclear requirements, and working with product and other teams to ship features. Prepare stories where the decision and the result were yours, and use the first person singular.

  1. Tell me about a time you took ownership of a project from start to release and what you would change.

How do you handle hidden data constraints or edge cases when parsing unfamiliar third-party API responses?

Medium · Python and API Integration

Approach

  1. Choose a real integration where a provider's response surprised you: missing or null fields, a field that changes type, pagination that stops early, timestamps in a different zone, duplicate records, or an error returned with a success status code. A generic claim that you read the docs carefully will not carry the answer.
  2. Tell the story as a sequence: how you discovered the gap (sampling real responses, logging unexpected shapes, a failing record), what you changed, and how you proved it. Typical fixes are a validation layer or schema model at the boundary, saved response fixtures used in tests, and explicit handling for unknown values.
  3. State the policy you chose for bad records and why: fail loudly, skip and log, or quarantine for review. For billing-style data, silently dropping a record is a risky default.
  4. Close with the result using numbers you really have, and what you added so a future change in the provider's response shape is detected quickly, such as an alert on parse-failure rate.

Follow-up

  • How did you find out the provider's docs and the real responses disagreed?
  • Why did you skip or quarantine bad records instead of failing the whole run?
  • How would you detect that the provider changed its response format without warning?

Assesses your communication skills, collaboration style, and alignment with the company culture.

Medium · Behavioral and Leadership

Approach

  1. Pick one story where communication changed the outcome, such as aligning an engineer, a product manager and a salesperson on what a feature would and would not do. Show what you actually did: a short written summary of requirements, a demo, an early risk message.
  2. Describe a real disagreement or misunderstanding and how it was resolved. Say what evidence or prototype settled it, and what you conceded, so the story shows collaboration rather than winning.
  3. Show you adapt to the audience: the detail you give a product manager, a sales engineering colleague and an engineering leader differs. Give one example of translating a technical trade-off into business terms.
  4. For the culture part, speak about how you work: shipping a thin version quickly, taking responsibility for a feature after release, and asking for help early. Do not recite values; candidates describe the environment as fast-paced and high-ownership, so show habits that fit that.

Follow-up

  • Tell me about a time you had to deliver bad news about a slipping schedule. How did you do it?
  • When you disagreed with a product manager and the decision went the other way, what did you do next?
  • How do you explain a technical trade-off to someone outside engineering?

Tell me about your background and how your past project experience prepares you for this role.

Medium · Behavioral and Leadership

Approach

  1. Structure it as present, past, future: what you do now, the two or three projects that built the skills relevant here, and why this role is next. Keep it short and finish by pointing at the role.
  2. Lead with the closest match to the work: a web app you shipped end to end, an integration with an outside API, or a data-handling backend. Name your own contribution, the stack (Python, React, SQL, a cloud platform if you used one) and a concrete outcome.
  3. Link the experience to the work candidates describe: parsing third-party API data, full-stack features, and a backend whose load grows. If you have healthcare or financial-systems experience, say so; if not, offer the closest equivalent such as payments, scheduling or data pipelines, without overstating it.
  4. Say what was personally yours versus what the team did, and avoid reciting your resume line by line. Be honest about the part of the stack that is newest to you and how you ramp up.

Follow-up

  • Which of those projects are you proudest of, and what would you change in it now?
  • What exactly did you write yourself, as opposed to your teammates?
  • Which part of this stack is newest to you, and how would you get productive in it?

How do you handle unclear instructions or ambiguous requirements when working on a technical task?

Medium · Behavioral and Leadership

Approach

  1. Pick a task where the instructions were truly unclear, ideally a technical one such as an API integration with incomplete docs or a feature request with no acceptance criteria. Show that you did not wait passively or guess silently.
  2. Walk through your method: list what is known and unknown, ask the person who owns the answer two or three specific questions, test unknowns directly (call the API, check real data), and write down the assumptions you are proceeding with.
  3. Show how you reduced risk: build the smallest reversible slice first, show it early, and confirm direction before building the rest. Explain how you decided when to stop asking and start building.
  4. Finish with the result and what you learned. State what happened when one assumption turned out to be wrong, because that shows how you correct course.

Follow-up

  • What did you do when the person who could clarify was unavailable?
  • How did you find out that one of your assumptions was wrong?
  • Tell me about a time you asked too many questions, or too few. What happened?

Describe a time you collaborated with cross-functional teams to deliver a product feature under a tight deadli

Medium · Behavioral and Leadership

Describe a time you collaborated with cross-functional teams to deliver a product feature under a tight deadline.

Approach

  1. Name the functions involved (product, design, sales engineering, operations or other engineers), the feature, and what was fixed about the deadline versus what was flexible. Keep the setup short.
  2. Spend the middle on your decisions: how you split the work, how you cut scope into must-have and defer, how you kept people aligned (a shared checklist, a daily check-in, a single owner for each dependency), and which risk you raised early.
  3. Include one friction point, such as a late requirement change or a blocked dependency, and how you handled it without blaming the other team.
  4. Report what shipped, what was deferred, and what followed after launch, such as the monitoring and clean-up you did. Use real numbers or feedback if you have them.

Follow-up

  • What did you cut to hit the deadline, and who agreed to it?
  • What technical shortcuts did you take, and did you pay them back afterwards?
  • What would you change about how the group coordinated if you did it again?