cswork / libraryOpen practice workspace

Interview guides · Amperos Health

Amperos Health Software Engineer Interview Guide

Amperos Health Software Engineer interview guide: rounds, coding, system design, Integrations and Infra questions, behavioral prompts and a 7-day plan.

The 60-second brief

Candidates describe Amperos Health as a company that builds infrastructure and applications for healthcare operations: software that handles sensitive data and moves information between medical systems. The openings they mention are split by track: Software Engineer, Infra (cloud environments), Software Engineer, Integrations (healthcare data exchange), and Full Stack and Design Engineers (user-facing applications for healthcare professionals). Reported day-to-day duties include writing, reviewing and testing code, running design reviews, writing technical specs, mentoring junior engineers, helping with production incidents and improving deployment practice, working alongside product managers, designers and QA engineers.

This guide is for candidates interviewing for a Software Engineer role at Amperos Health, on any of the tracks candidates mention: Infra, Integrations, Full Stack or Design.

Candidates report a recruiter screen, a technical phone screen in a shared coding environment, and a virtual onsite loop covering coding, system design, track-specific deep dives and behavioral alignment, over about 3-5 weeks.

Focus areas

  • Integrations Engineering
  • Cloud Infrastructure
  • Full-Stack Development

Overview

The role candidates describe covers more than one kind of engineer. Infra work centres on cloud environments and reliability, Integrations work on moving data between healthcare systems and third-party partners, and Full Stack and Design work on the interfaces healthcare professionals use. That spread explains the loop's shape: shared computer science fundamentals for everyone, then a deep dive that depends on the track. Decide which track you are targeting before you plan anything else, because it decides which half of your preparation is optional.

The reported questions fall into four groups. Five are coding problems: the longest substring without repeating characters, merging K sorted linked lists, finding peak concurrent users from login and logout times, serializing and deserializing a binary tree, and a thread-safe LRU cache. Five are system design prompts and they are a mixed set: a secure chat application for doctors, a crawler for public medical research papers, ingestion of large batch files from external hospital networks, a highly available URL shortener and a real-time leader-board. Only some of them are healthcare-specific, so do not prepare only for medical scenarios. Four are Integrations and Infra questions: API key rotation, debugging a latency spike, retries with exponential backoff and zero-downtime CI/CD. Five are behavioral.

Two themes run through the design and domain questions: unreliable boundaries and sensitive data. Batch file ingestion, retries against an external API, key rotation across hundreds of integrations and zero-downtime deployment all ask what happens when something fails halfway, so rehearse idempotency, dead-letter handling, backoff with jitter and rollback until you can say them without notes. Candidates also report that edge cases such as null input, unexpected data types and network timeouts should be raised out loud, and that showing a security-first habit helps even without healthcare experience.

The stack candidates report is broad. Python, TypeScript, Go and Java appear as example languages, along with SQL or NoSQL database design, API architecture, message queues such as Kafka and RabbitMQ, and for Infra candidates Kubernetes, AWS or GCP, Terraform and CI/CD. For Full Stack and Design, candidates mention React or Vue, TypeScript, state management and accessibility. HL7, FHIR, HIPAA and SOC2 appear as nice-to-have, and candidates report that healthcare experience is rarely a strict requirement. Pick one language to write every coding answer in, and spend the rest of your time on the track-specific list that matches your target.

The Amperos Health interview process

Candidates report three stages over about 3-5 weeks: a recruiter screen, a technical phone screen and a virtual onsite loop of four to five rounds. This is what candidates describe, not a published process.

Recruiter Screen

Candidates report that the recruiter screen is a conversation to align on your background, which role you are interested in and compensation expectations. Role interests can include a track choice, for example a New York-based Full Stack position versus Infra. Treat it as the point where you fix the track you will be interviewed for, because candidates report that the technical deep dive later depends on it.

What to demonstrate

  • How your background lines up with the openings
  • Candidates report the call covers background, role interests and compensation expectations.

How to prepare

  • Write a short pitch that names your track, your strongest project on it and the language you will code in, and say why healthcare software interests you in terms you can defend.
  • Settle your compensation expectations from your own market research before the call, and decide your answer on location and work arrangement.
  • Ask which track the later deep dive will follow, and what format the onsite rounds take, so you can aim your practice at it.

Technical Phone Screen

Candidates describe working one practical coding or data problem live with an engineer in a shared editor. Candidates report problems at LeetCode Medium level, and that correctness and clear communication matter more than speed. The reported coding questions are a reasonable way to practise for this format.

What to demonstrate

  • Whether you produce working, executable code in a shared editor
  • How you break a problem down and explain your reasoning as you go
  • Whether you handle edge cases and state time and space complexity

How to prepare

  • Practise the reported coding set (longest substring without repeats, merging K sorted lists, peak concurrent users, tree serialization, thread-safe LRU cache) in one language, typing into a plain editor and running your own tests.
  • Rehearse string and array parsing problems, since candidates link them to data ingestion work, and practise hash map, BFS and DFS patterns until the code comes out without lookup.
  • Build a habit for the first minutes: restate the input, ask about size and invalid values, give the brute-force idea, then the target complexity, then code.

Virtual Onsite Loop

Candidates report that the virtual onsite loop has four to five rounds, with coding, system design, domain-specific deep dives and behavioral topics among them. The deep dive follows your track: Infra candidates report Kubernetes, AWS or GCP, Terraform and CI/CD; Integrations candidates report third-party APIs, webhooks, data transformation pipelines and partner outages; Full Stack and Design candidates report React, TypeScript, state management, component design and browser performance. Prepare each reported category rather than guessing which round holds which question.

What to demonstrate

  • Coding fluency and the habit of testing your own code, as in the phone screen
  • Ability to turn an ambiguous design prompt into requirements, components, a data model and named bottlenecks
  • Practical depth in your track's tools and operational problems
  • Behavioral alignment: ambiguity, conflict, ownership and impact

How to prepare

  • Work the system design set end to end: secure doctor chat, batch file ingestion from hospital networks, crawler, URL shortener and real-time leader-board, each with requirements, storage, scaling and failure handling.
  • For Infra, rehearse the multi-region database failover plus key rotation, latency debugging and zero-downtime CI/CD; for Integrations, retries with backoff and partner outages; for Full Stack or Design, build an accessible calendar component with state handling.
  • Write five behavioral stories with real results, including one where you protected security or data integrity, and rehearse them aloud.

Where candidates lose points

Finishing the longest-substring or peak-concurrency solution without running a single edge case

After writing the code, trace three inputs by hand: an empty string or log, a case that stresses the logic (the repeated character at the window start in 'abba', or a login and logout at the same timestamp), and an unmatched login. Say your rule for ties and unclosed sessions aloud before you test.

Writing a thread-safe LRU cache where the lock covers only the map, or where get is treated as read-only

A get reorders the recency list, so it mutates shared state. Guard the map and the doubly linked list with the same lock, keep each operation's whole mutation inside it, and state the capacity invariant you are protecting. Offer sharded LRU segments, each with its own lock, as the follow-up, not the first answer. A concurrent map alone does not make the recency list thread-safe.

Drawing boxes for the chat, hospital batch ingestion or leader-board design without stating delivery semantics or failure behaviour

For every queue or store, say what happens on a duplicate message, a crash halfway through a file and a downstream outage. Name idempotency keys, a dead-letter path and what users see during recovery, and call out the sensitive-data controls: encryption, per-user access checks and audit logging.

Answering key rotation, latency debugging, retries or CI/CD by naming tools instead of giving a sequence

Give ordered steps. For latency: what changed, which percentile moved, which span or dependency is slow, then mitigation before root cause. For retries: retryable versus non-retryable errors, capped exponential backoff with jitter, an idempotency key, and a limit on total attempts. For rotation: two valid keys during a hand-over window, then revoke the old one.

Telling behavioral stories in the first person plural, or with no result you can explain

Pick stories where you made the call, state one measurable result and be ready to explain how you calculated it. Rehearse the five reported prompts, including why you want this company, with specifics you verified yourself.

Questions & practice

Write a function to find the longest substring without repeating characters.

Medium · Coding and Data Structures

Approach

  1. Sliding window: keep a left pointer and a map from character to the index where it was last seen. For each right index r, if s[r] was last seen at or after left, set left = last[s[r]] + 1. Then record last[s[r]] = r and best = max(best, r - left + 1).
  2. Explain why it is linear. The window never contains a duplicate, and each pointer only moves forward, so the time is O(n). Space is O(min(n, alphabet size)). Compare that with checking every substring, which is O(n^2) with a set per start index and O(n^3) if each substring is rescanned.
  3. Walk through "abba" to show the stale-index trap. When the second 'a' arrives, its last index (0) is already left of the window. Taking max(left, last + 1) keeps left from moving backwards.
  4. Edge cases: an empty string returns 0, all-identical characters return 1 and all-distinct characters return n. For ASCII input, a fixed int array of 128 or 256 entries can replace the map.
  5. Ask what a character is. Python iterates code points, while Java and JavaScript strings are UTF-16, so an emoji counts as two units unless you iterate code points.

Follow-up

  • Return the substring itself, not just its length.
  • Change it to the longest substring with at most k distinct characters.
  • How would this work on a stream where you cannot index back into the input?

Implement a method to merge K sorted linked lists.

Medium · Coding and Data Structures

Approach

  1. Use a min-heap that holds the current head of each non-empty list. Pop the smallest node, attach it to the tail of a dummy node and push that node's next. With N total nodes this is O(N log k) time and O(k) heap space, and it relinks the existing nodes instead of allocating new ones.
  2. In Python, heapq compares tuples element by element and list nodes are not comparable, so push (value, tie_breaker_index, node). In Java, give PriorityQueue a comparator on node.val.
  3. Alternative: divide and conquer. Merge the lists in pairs over log k rounds, each costing O(N), for O(N log k) total with no heap. Merging one list at a time into an accumulator costs O(kN) in the worst case, which is why it loses.
  4. Edge cases: an empty input array, null lists inside the array, a single list, duplicate values and very unequal lengths. Skip null heads when you build the heap.

Follow-up

  • How would you merge k sorted files too large to fit in memory? (k-way external merge with buffered readers)
  • When would you pick the pairwise merge over the heap, or the other way round?
  • What changes if the lists are live streams that keep receiving values?

Given a log file of user login/logout times, find the peak period of concurrent active users.

Medium · Coding and Data Structures

Approach

  1. Turn each session into two events: (login_time, +1) and (logout_time, -1). Sort the events by time and sweep with a running count, recording the maximum and the timestamp where it is reached. The peak period runs from that event to the next event's timestamp. This is O(n log n) for the sort and O(n) space.
  2. State the tie rule for equal timestamps. If sessions are half-open [login, logout), process -1 before +1 at the same time, so that someone leaving and someone arriving at the same moment do not overlap.
  3. Clean the log before sweeping. Pair events per user or per session id, and decide what to do with a logout that has no login (drop it) and a login that has no logout (count it as active until the end of the log or a session timeout). Also handle duplicate lines, out-of-order lines and mixed time zones.
  4. If timestamps are integer seconds in a known range, a difference array plus a prefix sum gives O(n + range) and avoids the sort. If several intervals tie for the maximum, return all of them and merge adjacent ones.

Follow-up

  • Should this count distinct users or sessions when one user is logged in on two devices?
  • How would you compute the peak for a log too large to sort in memory?
  • How would you return the peak for every day of the log?

Write an algorithm to serialize and deserialize a binary tree.

Medium · Coding and Data Structures

Approach

  1. Serialize with a preorder DFS that writes each value plus a null marker such as '#', joined by a delimiter. To deserialize, read the tokens through an iterator and recurse: a null token returns None, otherwise build the node, then its left subtree, then its right subtree. Both directions are O(n) time and space.
  2. The null markers are what make a single preorder traversal unambiguous. Without them you need two traversals (preorder plus inorder), and even that only works when the values are unique.
  3. Alternative: level-order with a queue, writing nulls for missing children and trimming trailing nulls. An iterative version avoids recursion-depth limits on skewed trees (Python's default limit is about 1000 frames).
  4. Edge cases: an empty tree, negative and multi-digit values (this is why the delimiter is needed), a single node and a degenerate linked-list-shaped tree. If the values are strings, escape the delimiter or length-prefix each token.

Follow-up

  • If the tree is a BST, can you drop the null markers? (Yes: rebuild from preorder using value bounds in O(n).)
  • How would you extend this to an N-ary tree?
  • How would you make the format compact and versioned for storage?

Implement a thread-safe LRU Cache.

Medium · Coding and Data Structures

Approach

  1. Start with the single-threaded core: a hash map from key to node, plus a doubly linked list in recency order with sentinel head and tail. get moves the node to the front. put inserts at the front, or updates and moves an existing key, and evicts the tail when size exceeds capacity. Both are O(1).
  2. Know the library shortcuts. Python has OrderedDict with move_to_end and popitem(last=False). Java has LinkedHashMap(capacity, loadFactor, true) with removeEldestEntry overridden.
  3. For thread safety, note that get also changes the list order, so a read-write lock gains nothing. Every operation is a write. The simple correct version wraps each operation in one mutex (threading.Lock, synchronized or ReentrantLock).
  4. To reduce contention, shard by key hash into independent LRU segments, each with its own lock. The trade-off is that eviction becomes LRU per shard, not global. A concurrent map alone does not make the recency list thread-safe. Approximate recency (CLOCK, sampled eviction or buffered access updates) gives up exact order for throughput.
  5. Pitfalls: a get-then-put on a miss is not atomic, so two threads can both load the same key. Use a compute-if-absent style loader with per-key in-flight deduplication, and never hold the cache lock while calling a slow loader. Also test capacity 0 and 1.

Follow-up

  • Add a per-entry TTL. Where do expired entries get removed?
  • How would you test this for race conditions?
  • How do you stop many threads from loading the same missing key at once?

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?

Find version gaps and relay lag with window functions

Medium · window functions · event tables · frame semantics

outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.

Approach

  1. Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
  2. Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
  3. Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
  4. Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.

Follow-up

  • Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
  • Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?

Design a real-time chat application for doctors to communicate securely about patient care.

Medium · System Design

Approach

  1. Pin down the requirements first. Functional: 1:1 and group conversations (for example, a care team around one patient), message history, offline delivery and read receipts. Non-functional: ordering within a conversation, no lost or duplicated messages, and protection of patient data under HIPAA.
  2. Real-time path: clients keep a WebSocket to a fleet of stateless gateway servers. A presence registry (for example, Redis) maps each user to their gateway. A message service checks membership, persists the message, assigns a per-conversation sequence number, then fans out through pub/sub to the online members' gateways. Offline members get a push notification with no clinical content.
  3. Storage: key messages by (conversation_id, seq) in a store partitioned by conversation_id. That makes reading a conversation cheap and reading one user's recent messages across conversations expensive, so keep a separate per-user inbox index.
  4. Reliability: the client generates a message id, so a resend after a reconnect is deduplicated. On reconnect the client fetches everything after the last sequence number it saw.
  5. Security: TLS in transit, encryption at rest with KMS-managed keys, SSO with MFA, a membership check on every send and fetch, an audit log of who read what, and no message bodies in logs or push previews. Weigh end-to-end encryption: it protects content better, but it makes server-side search, retention, audit and multi-device key handling harder.

Follow-up

  • Two doctors post in the same group chat at the same moment. How do all clients agree on the order?
  • A gateway holding tens of thousands of connections dies. What happens to those users and their in-flight messages?
  • How would you support search if message bodies are encrypted?

How would you design a distributed web crawler to aggregate public medical research papers?

Medium · System Design

Approach

  1. Scope it first: which sources (publisher sites, preprint servers, institutional repositories), how many papers, and how fresh the data must be. Prefer structured feeds (repository APIs, OAI-PMH, sitemaps) over HTML scraping where they exist, and respect robots.txt and each site's terms.
  2. Components: a URL frontier (priority queue plus per-host queues), fetcher workers with a DNS cache, a parser that pulls metadata from HTML and PDFs, a deduplication stage, and storage split into raw blobs, a metadata database and a search index.
  3. Politeness and partitioning: assign hosts to workers by hashing the hostname, so one worker owns each host's rate limit and crawl-delay. Back off on 429 and 503 responses.
  4. Two kinds of deduplication. URL level: normalize URLs and keep a seen-set, where a Bloom filter saves memory but its false positives skip a few new URLs. Paper level: the same paper shows up as a preprint, a published version and mirrors, so dedupe by DOI, falling back to normalized title plus authors. Content hashes differ between a preprint and the published version.
  5. Robustness: keep the frontier in a durable queue so a worker crash loses nothing, cap depth and URL length to escape spider traps, and schedule recrawls by each source's observed change rate.

Follow-up

  • How do you match the same paper across sources when there is no DOI?
  • Some sources render their listings with JavaScript. What changes?
  • What breaks first when the number of hosts grows tenfold?

Design a system to handle the ingestion and processing of large batch files from external hospital networks.

Medium · System Design

Approach

  1. Clarify the inputs: file formats (CSV, fixed-width, HL7 v2 batches, FHIR bulk NDJSON), file sizes, how files arrive (SFTP drop, upload API, shared bucket), the processing deadline, and how much each hospital's schema differs.
  2. Pipeline: land each file unchanged in encrypted object storage and verify its checksum. Create a job record with an explicit state machine (received, validating, processing, loaded, failed). Split the file into chunks for queue-fed workers, write to staging, and promote to final tables atomically.
  3. Idempotency: identify a file by partner, file name and checksum, so a re-delivery does not load twice. Upsert records on a natural key, and checkpoint each chunk so a retry resumes rather than starting over.
  4. Validation: a mapping config per partner instead of code per partner. Send bad rows to a quarantine table with line numbers and reasons, reject the whole file above an error threshold, and report results back to the partner.
  5. Operations and PHI: wait for a completion marker or manifest before reading, so you never process a half-uploaded file. Alert when an expected file is late or missing. Apply least-privilege access, audit logs and a retention policy for raw files.

Follow-up

  • A hospital resends a corrected file for the same day. How do you replace the first load instead of appending to it?
  • A very large file has one malformed row in the middle. What happens?
  • How would you onboard a hospital with a new format without a code change?

Architect a highly available URL shortener service.

Medium · System Design

Approach

  1. API: POST creates a short code from a long URL, with an optional custom alias and expiry. GET /{code} redirects. Traffic is read-heavy. A 301 is cached by browsers, which cuts load but hides later clicks from analytics. A 302 sends every click through the service.
  2. Code generation: base62-encode a unique id. Seven characters give 62^7, about 3.5 trillion codes. Have each app node reserve a block of ids from a counter, so there is no hot counter on every write. Sequential codes can be guessed, so if that matters, use random codes with insert-if-absent and retry on collision.
  3. Storage and reads: a replicated key-value store keyed by code, a cache (Redis and/or the CDN) in front of the redirect path, and click analytics written asynchronously through a queue, never inline with the redirect.
  4. High availability: stateless servers behind load balancers in several zones or regions. A mapping never changes after creation, so replicas and caches can serve reads safely, apart from deletes and expiry. Give each region its own id range so writes need no cross-region coordination.
  5. Abuse controls: rate-limit link creation, scan target URLs for malware, and keep a way to disable a code that also purges it from caches.

Follow-up

  • How do you handle two users asking for the same custom alias at the same time?
  • How do you expire links, and would you ever reuse their codes?
  • A link is disabled for abuse but stays cached at the edge. How do you get it removed?

Design a leader-board system that updates in real-time for millions of concurrent users.

Medium · System Design

Approach

  1. Clarify what is needed: the global top N, a single user's rank, friends-only boards, how scores combine (best or sum), daily or weekly windows, and how ties are ordered.
  2. Core structure: a Redis sorted set. ZADD or ZINCRBY updates a score in O(log N). ZRANGE with REV returns the top M in O(log N + M), and ZREVRANK returns a user's rank in O(log N). Keep a separate key per time window and set it to expire.
  3. Ties: fold a timestamp into the score so the earlier achiever ranks higher. Scores are doubles, which represent integers exactly only up to 2^53, so check that the packed value fits.
  4. Scale: one sorted set lives on one node, and writes to it are serialized. To shard, partition by score range, where rank equals the count in all higher shards plus the rank within the user's shard. Or keep the top K exact and give everyone else an approximate rank from a score histogram.
  5. Durability and fan-out: write score events to a durable log and database, and rebuild Redis from them after a failure. Do not push every change to millions of clients. Push throttled top-N snapshots over WebSocket or SSE, and return a user's own rank when asked.

Follow-up

  • How do you show a user who is ranked four millionth their rank cheaply?
  • How would you reject cheated or impossible scores?
  • The Redis node holding the board fails. How do you recover, and what do users see meanwhile?

How do you securely manage and rotate API keys for hundreds of third-party integrations?

Medium · Domain-Specific (Integrations & Infra)

Approach

  1. Keep keys in a secrets manager (AWS Secrets Manager, GCP Secret Manager or Vault), encrypted with KMS. Never put them in code, committed config, container images or logs. Use per-service IAM so each integration can read only its own partner's secret, and audit every read.
  2. Keep an inventory record per secret: owner, partner, scopes, creation date, expiry, rotation policy and last use. Alert before a key expires, and flag keys nobody has used so they can be revoked.
  3. Rotate with overlap. Create the new key (through the partner's API if it has one) and store it as a pending version. Have consumers pick it up and verify it with a test call. Promote it to current, then revoke the old key after a grace window. Running two valid keys during the switch is what avoids downtime.
  4. On the consumer side, fetch secrets at runtime with a short cache. On a 401, refresh the secret once and retry before failing, so a rotation never needs a redeploy.
  5. Where partners support it, use OAuth client credentials with short-lived tokens or mTLS instead of long-lived keys. Run secret scanning on repositories, and keep an emergency revoke-and-reissue runbook for leaks.

Follow-up

  • A partner allows only one active key at a time. How do you rotate without an outage?
  • How do you prove to an auditor who accessed a given key and when?
  • How do you find out which of hundreds of keys are still in use?

Walk me through the steps you would take to debug a sudden spike in latency across a microservice architecture

Medium · Domain-Specific (Integrations & Infra)

Walk me through the steps you would take to debug a sudden spike in latency across a microservice architecture.

Approach

  1. Scope the spike first: which endpoints, p50 or only p99, one region or all, and the exact start time. Then check what changed at that moment: deploys, config or feature-flag changes, traffic shape, or an incident at a dependency. If a deploy lines up with it, roll back first and investigate after.
  2. Find where the latency starts. A caller's latency includes its callees', so walk the dependency graph using per-service rate, error and duration metrics until you reach the deepest service whose own latency rose. Distributed traces of slow requests (OpenTelemetry, Jaeger) show which span is taking the time.
  3. Check the usual causes there: CPU or memory saturation, thread or connection-pool exhaustion, GC pauses, slow queries or lock contention in the database, a falling cache hit rate, network or DNS problems, and retry storms where timeouts trigger retries that add load.
  4. Break the numbers down by instance and percentile. Averages hide the tail, and a single bad host or pod can drive the p99.
  5. Mitigate, then fix: scale out, shed load, open a circuit breaker or turn off a flag. Confirm the metrics return to baseline, then write the root cause up with a new alert on the earliest signal you saw.

Follow-up

  • Traces show time spent in a database call, but the database's own metrics look normal. What do you check next?
  • Only p99 rose and p50 is flat. What does that suggest?
  • How would you debug this without any distributed tracing?

Describe how you would implement a robust retry mechanism with exponential backoff for an unreliable external

Medium · Domain-Specific (Integrations & Infra)

Describe how you would implement a robust retry mechanism with exponential backoff for an unreliable external API.

Approach

  1. Retry only failures that can succeed on a second try: connection errors, timeouts, 429, 502, 503 and 504. Do not retry 400, 401, 403, 404 or 422. Honour a Retry-After header when the API sends one.
  2. Delay formula: delay = min(cap, base * 2^attempt) with full jitter, meaning a random value between 0 and that number. Jitter stops many clients from retrying in lockstep. Bound both the number of attempts and the total time spent.
  3. A timeout does not mean the request failed, so make retries idempotent. GETs are safe. For writes, send an idempotency key if the API supports one, or check whether the first attempt took effect before resending.
  4. Protect both sides with a circuit breaker after repeated failures and a retry budget (retries capped at a fraction of normal traffic). Retry at one layer only: three layers each making up to three attempts send 27 calls to the bottom service.
  5. For long outages, do not sleep in the request thread. Move the work to a durable queue with delayed redelivery and a dead-letter queue, and track the retry count and dead-letter depth.

Follow-up

  • How would you unit-test the backoff without real sleeps? (injected clock, scripted failures)
  • The partner returns 200 with an error in the body. How does your retry logic handle it?
  • A retried older update lands after a newer one. How do you prevent stale data from winning?

How would you design a CI/CD pipeline to ensure zero-downtime deployments for a critical healthcare applicatio

Medium · Domain-Specific (Integrations & Infra)

How would you design a CI/CD pipeline to ensure zero-downtime deployments for a critical healthcare application?

Approach

  1. CI: on every change, run lint, unit and integration tests, a dependency scan and a static security scan. Then build one immutable artifact (a container image tagged with the commit SHA) and promote that same artifact through staging to production.
  2. Choose a release strategy and its trade-off. A rolling update with readiness probes (in Kubernetes, maxUnavailable 0 and maxSurge 1) is cheap. Blue-green is a load-balancer switch with instant rollback but doubles capacity. A canary sends a small share of traffic to the new version, with automatic rollback on errors or latency.
  3. Make shutdown graceful: handle SIGTERM, stop receiving new requests (Kubernetes removes the pod from endpoints and runs a preStop hook) and let in-flight requests finish, so a rollout drops no connections.
  4. Database changes follow expand/contract. Old and new code run side by side during a rollout, so first add the new columns or tables, then deploy code that writes both, backfill, switch reads, and remove the old schema in a later release. Never ship a breaking migration in the same deploy as the code.
  5. Separate deploy from release with feature flags, run smoke tests after each deploy, and keep the previous version ready to roll back. For a healthcare application, add audit records of who deployed what, approval gates for production, secrets from a manager, and test data with no real patient records.

Follow-up

  • A column the running version reads must be renamed. Walk through the deploys.
  • The canary metrics look healthy, but one customer's integration breaks. How would you catch that?
  • What cannot be undone by rolling back, and how do you plan for it?

One log partition stops advancing while the others drain

Easy · poison message · head-of-line blocking · consumer lag

Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.

Approach

  1. Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
  2. Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
  3. Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
  4. Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.

Follow-up

  • The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
  • How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?

Suggested practice checklist

The plan follows the reported stages: confirm the track and pitch, then the coding set, the system design set, the Integrations and Infra set (with a Full Stack or Design alternative) and the behavioral stories, ending with a mixed rehearsal. Each day produces something you can check.

Day 1 — Pick your track and write your pitch

  • Write the reported sequence (recruiter screen, technical phone screen, virtual onsite loop of four to five rounds) and mark which part you are least ready for.
  • Choose your track and list the stack candidates report for it: Kubernetes, AWS or GCP, Terraform and CI/CD for Infra; third-party APIs, webhooks and data transformation for Integrations; React, TypeScript, state management and browser performance for Full Stack or Design.
  • Write a short pitch and a first answer to why you want to work at Amperos Health, using facts you have read.

Deliverable: A one-page loop map, a track stack list and a written pitch with a first answer to the why-this-company question.

Day 2 — Strings, hashing and event sweeps

  • Solve the longest substring without repeating characters with a sliding window and a last-seen index map; state O(n) time and the space bound, and test empty input, all-identical characters and 'abba'.
  • Solve the peak concurrent users question by turning each login and logout into +1 and -1 events, sorting by time and sweeping with a running count; write down your rule for equal timestamps and for logins with no logout.
  • Run both in a plain editor with your own tests and explain each aloud once.

Deliverable: Two working solutions in your chosen language, each with a written list of passing edge-case tests.

Day 3 — Heaps, trees and a thread-safe cache

  • Write merge K sorted linked lists with a min-heap keyed on value and list index (O(N log k)), then write the pairwise merge version and compare memory.
  • Write serialize and deserialize for a binary tree using preorder traversal with null markers, and verify round trips on an empty tree, a skewed tree and negative or multi-digit values.
  • Build a thread-safe LRU cache from a hash map and doubly linked list under one lock, then write a multi-threaded test that checks capacity is never exceeded.

Deliverable: Three tested implementations plus a short note on the complexity and the locking choice for each.

Day 4 — Design: doctor chat and hospital batch files

  • Design the secure chat for doctors: connection layer, message service, durable store, per-conversation ordering, delivery and read state, offline notification, encryption and access checks.
  • Design batch file ingestion from external hospital networks: upload to storage, queue, parsing workers, validation, idempotency by file identity, a dead-letter path and a partial-failure report.
  • For each, write the requirements you would ask about and three failure modes with their handling.

Deliverable: Two one-page designs, each with a diagram of components, a data model and three named failure modes.

Day 5 — Design: URL shortener, leader-board and crawler

  • Design a highly available URL shortener: key generation without collisions, read-heavy caching, redirect behaviour and failover across regions.
  • Design a real-time leader-board for millions of concurrent users: a score store with ordered ranking, sharding, top-N versus a single user's rank, and what you approximate at scale.
  • Design the distributed crawler for public medical research papers: URL frontier, per-host politeness, URL normalization, paper deduplication by DOI (falling back to normalized title plus authors, since a preprint and its published version have different content hashes), and retries.

Deliverable: Three design outlines, each with a rough capacity estimate and its first bottleneck written down.

Day 6 — Integrations, Infra and Full Stack deep dives

  • Outline API key management for hundreds of integrations: a secret store, per-integration keys, an overlap window with two valid keys, revocation, least privilege and audit logs.
  • Write your latency-spike debugging sequence: what changed, which percentile moved, trace to the slowest dependency, check saturation and retry storms, mitigate, then find the cause.
  • Write the retry design (capped exponential backoff, jitter, idempotency keys, retryable versus non-retryable errors) and a CI/CD outline with rolling or blue-green rollout, health checks and backward-compatible migrations.
  • Track pick. Infra: outline the reported multi-region failover for the core database (replication mode, RPO and RTO, promotion, split-brain fencing). Full Stack or Design: build the reported accessible telemedicine calendar component in React and TypeScript with keyboard navigation, ARIA roles and state handling.

Deliverable: Four written runbooks (key rotation, latency debugging, retries, zero-downtime pipeline), the completed debugging drill, and either a failover outline or a working calendar component for your track.

Day 7 — Behavioral stories and a mixed rehearsal

  • Write outlines for the five reported behavioral prompts: a decision with incomplete information, a disagreement with a senior engineer, a process bottleneck, a late project, and why you want to work at Amperos Health. Give each one situation sentence, your decision, the evidence and a result you can defend.
  • Prepare one security-first example from your own work and one story from your track, such as integrating an undocumented third-party API or building an accessible interface.
  • Do a mixed run aloud: redo the thread-safe LRU cache, then talk through the doctor chat design start to finish.

Deliverable: Five written story outlines, one security example, and a recorded or noted mock run of one coding and one design answer.

Behavioral stories for the onsite loop

Candidates report behavioral alignment as part of the virtual onsite loop, with topics such as handling ambiguity, disagreeing with peers or product managers on technical decisions, and owning a project end to end with a measurable result. They also advise STAR structure with most of the time on your action and result, and a security-first example. Prepare stories where you made the decision, give the measure that changed, and say what you would do differently.

  1. Tell me about a time you had to push back on a product requirement because of technical debt or security concerns.

Tell me about a time you had to make a critical technical decision with incomplete information.

Medium · Behavioral and Leadership

Approach

  1. Choose a decision you made yourself, and name the unknown precisely: a missing load figure, an undocumented partner API, a schema nobody could confirm. A vague 'not enough data' story gives nothing to evaluate.
  2. Show how you bounded the uncertainty: separate reversible from irreversible choices, run the cheapest experiment or spike that could change your mind, and fix a decision point so analysis did not drag on.
  3. State the options, the criterion you chose on (risk, cost, speed, ability to undo) and the safeguard you put around the choice, such as a feature flag, a canary or a rollback path.
  4. Report what happened with a real measure, including what you got wrong if you did, and what you now do earlier, for example ask for load numbers or write the assumption down at the start.
  5. Common mistake: presenting the decision as obvious in hindsight, or answering 'I trusted my gut' with no reasoning.

Follow-up

  • Which assumption turned out wrong, and how did you find out?
  • How did you decide the decision was reversible enough to make without more data?
  • Would you make the same call with the same information today?

Describe a situation where you disagreed with a senior engineer's architectural proposal. How did you handle i

Medium · Behavioral and Leadership

Describe a situation where you disagreed with a senior engineer's architectural proposal. How did you handle it?

Approach

  1. Pick a technical disagreement about an architecture decision (a datastore, a sync versus queue boundary, a service split) where both sides had merit, not a case where the other engineer was plainly wrong.
  2. State the senior engineer's reasoning fairly before yours, then say the concrete risk you saw: a failure mode, a cost curve, an operational burden, a security gap.
  3. Show how you moved the discussion to evidence: a small prototype, a load estimate, a failure scenario walked through together, or a written comparison of options against shared goals.
  4. Describe the outcome honestly. Either the evidence changed one mind, you found a middle option, or you disagreed and committed to the chosen direction and helped it succeed; say what you did after the decision.
  5. Common mistake: a story where you went around the person, won by escalating, or described them as a blocker. Keep the account about the design and the evidence.

Follow-up

  • What if they had still disagreed after you showed the data?
  • How did you decide this was worth raising rather than letting it go?
  • How was your working relationship with them afterwards?

Tell me about a time you identified a significant bottleneck in your team's process and how you fixed it.

Medium · Behavioral and Leadership

Approach

  1. Name the bottleneck concretely and show how you found it with something measurable: pull request wait time, flaky CI runs, a manual release checklist, an overloaded on-call rotation. Say what the number was before.
  2. Explain what you considered and why you picked your fix: automate, remove a step, change ownership, split a queue. Show you weighed cost against the time it would save.
  3. Cover adoption. A fix only you use is not a team fix, so describe how you got buy-in, documented it or wired it into the pipeline so it stuck.
  4. Close with the before-and-after measure, using numbers you can defend and stating how long you watched it, and mention any side effect you had to manage.
  5. Common mistake: a heroic one-off clean-up with no measurement, or a result figure you cannot explain when asked how it was calculated.

Follow-up

  • How did you know the bottleneck was the real constraint and not just the most visible one?
  • Who resisted the change, and what did you do about it?
  • What would you automate next, and what is the risk of automating it?

Describe a project that took significantly longer than expected. What went wrong, and what did you learn?

Medium · Behavioral and Leadership

Approach

  1. Choose a project where the delay was partly your own misjudgment, not only a vendor or another team. Give the original estimate basis and what the real work turned out to be.
  2. Name the root cause precisely: hidden integration work, unclear requirements, a dependency that moved, scope that grew, or an estimate made without a spike. Then say when you first noticed the slip.
  3. Walk through what you did once you noticed: re-planned, cut or phased scope, told stakeholders early with a new date and the trade-off, and protected the parts that had to be correct.
  4. End with a specific change in practice that you have since applied, for example time-boxed spikes before estimating integration work, milestone check-ins, or earlier partner testing, and evidence it worked on a later project.
  5. Common mistake: blaming others, or turning the lesson into a strength disguised as a weakness such as caring too much about quality.

Follow-up

  • When did you first suspect the date was wrong, and what did you do that day?
  • How did you communicate the new timeline to people depending on you?
  • How do you estimate similar work now?

Why do you want to work at Amperos Health, and what impact do you hope to have here?

Medium · Behavioral and Leadership

Approach

  1. Anchor the answer in facts you have checked. Candidates describe the company as building software for healthcare operations, handling sensitive data and connecting medical systems, with tracks across Infra, Integrations, Full Stack and Design; check this against what the company publishes and mention something you actually found.
  2. Link that to your own experience in your chosen track, for example reliable data movement between systems, secure handling of credentials, or accessible interfaces, so the answer reads as a match rather than as praise.
  3. Describe the impact you could aim for in terms of work you would do, such as making a data exchange path safer to retry or a deployment path safer to ship, without claiming knowledge of internal plans.
  4. If you lack healthcare experience, say so and name what you would learn first, such as HL7 and FHIR basics and HIPAA handling. Candidates report this experience is rarely a strict requirement.
  5. Common mistake: a generic mission statement that fits any health-tech employer, or flattery in place of specifics.

Follow-up

  • Why health-tech rather than another domain where your skills apply?
  • Which track suits you best, and what would you want to learn in your first months?
  • What would make you leave a role like this?