cswork / libraryOpen practice workspace

Interview guides · Artera

Artera Software Engineer Interview Guide

Artera Software Engineer interview guide: reported rounds, coding, system design and behavioral questions, plus a 7-day TypeScript-focused prep plan.

The 60-second brief

Artera, as candidates describe it, builds a patient communication platform for healthcare. It sends automated messages to patients over several channels on behalf of health systems, and it connects to Electronic Health Record (EHR) and Electronic Medical Record (EMR) systems to do so. A Software Engineer there works on the services behind that messaging, in TypeScript and related backend and frontend languages. The reported responsibilities also include the pipelines that move data to and from EHR systems, watching production for slow spots, refactoring older code, and working alongside product managers, UX designers, QA engineers and customer success teams.

This guide is for engineers preparing for the Software Engineer loop at Artera; candidates describe the role as asking for around three or more years of professional experience. It covers the four reported rounds, all fifteen reported questions (five coding, five design and architecture, five behavioral), the example scenarios candidates mention, and a seven-day plan that practises them in TypeScript with PostgreSQL and REST APIs in mind.

Candidates report four rounds over roughly 3-5 weeks: a recruiter screen, an introductory conversation with a hiring or engineering manager, a technical evaluation with live coding and system design, and a virtual on-site loop with engineering, product and leadership members.

Focus areas

  • System Design
  • Coding Challenges (Live Coding)
  • TypeScript

Overview

The work candidates describe is message delivery at volume plus the plumbing to external healthcare systems. Day to day that means TypeScript services, PostgreSQL, REST APIs, integrations that must tolerate slow or rate-limited EHR endpoints, and refactoring older services without disturbing live customers. The reported questions mirror this: a notification dispatch system, an EHR synchronisation service under strict rate limits, a clinician-to-patient chat, a multi-tenant permissions schema, and a monolith-to-microservices migration with no downtime.

The fifteen reported questions split evenly. Five are coding: comparing two binary trees, parsing a legacy format into clean JSON, a small game or state-machine build, explaining the complexity of your own code, and aggregating notification log events by status inside a time window. Five are design and architecture, as listed above. Five are behavioral, covering legacy code, disagreement with a senior engineer or product manager, explaining a hard problem to non-technical people, working in a remote-first team, and learning something new fast. Candidates also mention scenario prompts such as a rolling-window rate limiter, flattening and validating nested JSON, diffing two hierarchies, routing inbound SMS replies, and a HIPAA-conscious document store.

Preparation therefore has three tracks. Build TypeScript fluency in a plain editor, with typed data structures and edge cases handled before you are asked. Practise design answers that name the failure behaviour of every dependency: retries with backoff, idempotency, dead-letter queues, circuit breakers and rate budgeting. Finally, prepare four or five personal stories with real decisions in them. Candidates list HL7, FHIR, RabbitMQ or Kafka and AWS as nice-to-have, so read enough about each to use the vocabulary correctly, and be straightforward about what you have not used.

The Artera interview process

These stages are what candidates describe rather than a published process, and they say the exact number of rounds can shift a little with team and location.

Recruiter Screen

Candidates report a first call with a recruiter to check qualifications and fit. No technical work is described for this stage, so the aim is a clear, short account of your background that lines up with what the role asks for: TypeScript or modern JavaScript, a relational database such as PostgreSQL, REST API design, and experience with high-throughput distributed web applications.

What to demonstrate

  • Your background against the requirements candidates describe for the role
  • Basic fit for a patient-messaging and healthcare-integration product
  • How clearly you summarise your own experience

How to prepare

  • Write a short walk through your last two roles in terms of the role's stack: TypeScript or JavaScript services, PostgreSQL, REST APIs, and anything high-throughput or distributed.
  • List which nice-to-have items you have actually used (HL7 or FHIR, RabbitMQ or Kafka, AWS) and prepare one honest sentence for each one you have not.
  • Prepare a reason for wanting to build software that sends automated messages for health systems, based on what the product does.
  • Write down questions about the remaining rounds so you leave the call knowing what the technical evaluation involves.

Introductory Conversation

Candidates report a conversation with a hiring manager or engineering manager about the role and what it involves. It is a discussion rather than a technical exercise as described, so the useful preparation is being able to connect your experience to the reported responsibilities: services, EHR integrations, production reliability, refactoring older code, and working with product, UX, QA and customer success.

What to demonstrate

  • How your experience maps onto the reported responsibilities
  • How you describe past work and trade-offs in conversation
  • Whether your expectations of the role match what is described

How to prepare

  • Pick two projects from your own history that resemble message delivery, integrations or reliability work, and be ready to describe your specific contribution to each.
  • Rehearse your legacy-code and remote-team stories from the behavioral section, since a conversation about experience draws on them.
  • Prepare questions about how engineering works with product managers, UX and customer success, and about what the first months in the role involve.

Technical Evaluation

Candidates report live coding and system design sessions in this stage. Coding happens in a shared online editor, usually in TypeScript or JavaScript, with one or two engineers present, and candidates say the design sessions use a collaborative diagramming tool. Some candidates also report exercises that run long, so the approach is to clarify requirements, state assumptions, get the core path working first, then handle edge cases and complexity.

What to demonstrate

  • Working code in TypeScript or JavaScript, using sensible data structures such as maps, sets, arrays and trees
  • Readable, modular code with error handling
  • Design of reliable, high-volume systems with external dependencies, including data modelling, queuing and failure handling
  • Explaining trade-offs aloud while you work

How to prepare

  • Practise in a bare editor without autocomplete: the binary-tree comparison, the legacy-format parser, the game or state-machine build, and the windowed aggregation of notification log events.
  • Write a rolling-window rate limiter class with an injectable clock, since candidates describe that scenario.
  • Draw the notification dispatch, EHR synchronisation, chat and multi-tenant schema designs on a diagramming tool, labelling queues, stores, retry paths and what happens when a dependency fails.
  • Practise explaining complexity for code you wrote, and decide beforehand which edge cases you will mention without being asked.

Virtual On-Site Loop

Candidates describe a virtual on-site loop of several interviews with members of the engineering, product and leadership teams. Expect the technical themes from the evaluation to continue alongside conversations about collaboration, so you need both a precise technical version and a plain-language version of each of your stories and designs.

What to demonstrate

  • Communication with engineers, product people and leaders in the same loop
  • Consistency between your stories, designs and stated experience across interviews
  • Collaboration themes the reported questions cover: legacy code, disagreement, remote work and fast learning

How to prepare

  • Prepare two versions of each behavioral story: a short one for product or leadership listeners and a detailed one for engineers.
  • Practise describing a design trade-off in terms of patient-facing outcomes, such as a reminder arriving late or twice, as well as in technical terms.
  • Read enough about HIPAA, message delivery reliability and EHR integration constraints to discuss data privacy sensibly in a design answer.
  • Keep a short list of questions for each type of interviewer, covering engineering practice, product collaboration and team direction.

Where candidates lose points

Writing the binary-tree comparison or the windowed log aggregation for the happy path only

For the tree, handle both-null, one-null and value mismatch first, define whether identical means structure only or structure and values, and mention the recursion depth on a skewed tree plus an iterative option. For the log aggregation, state whether the window is inclusive or exclusive at each end, how you treat unsorted events and unknown statuses, and what the output is for an empty window.

Running out of time on a long exercise because you started coding before settling the scope

Spend a minute restating the requirements and assumptions aloud, build the core path first, keep a short list of edge cases you will return to, and leave a few test calls in the editor. For the parser and game-state questions, get one valid input working end to end before widening it.

Designing the dispatch or EHR synchronisation system with retries but no idempotency, or ignoring the rate limit

Give each message a stable idempotency key per channel so a retry cannot send a second SMS, record status transitions, use exponential backoff with jitter, route exhausted messages to a dead-letter queue, and budget outbound EHR calls with a shared token bucket and incremental, cursor-based sync that respects 429 responses.

Answering the multi-tenant schema question with tables but no access-path reasoning

Put a tenant identifier on every row and lead indexes with it, model the hierarchy explicitly (adjacency list with a recursive query, closure table or ltree), attach permissions to nodes with a clear inheritance rule, and discuss how to enforce isolation in PostgreSQL, for example with row-level security.

Telling behavioral stories in the plural, with no decision of your own and no result

Use 'I' for what you decided and did, name the trade-off you weighed, and end with a measurable outcome or a concrete lesson. Check each of the legacy-code, disagreement, stakeholder-explanation and fast-learning stories against that standard before the loop.

Questions & practice

Implement a function to perform a deep comparison between two binary trees and return whether they are structu

Medium · Coding & Algorithms

Implement a function to perform a deep comparison between two binary trees and return whether they are structurally identical.

Approach

  1. Clarify what "structurally identical" means: shape only, or shape plus equal node values. Then define the TypeScript type, for example interface TreeNode { val: number; left: TreeNode | null; right: TreeNode | null }.
  2. Recursive solution: if both nodes are null return true; if exactly one is null return false; compare values if they count; then return same(a.left, b.left) && same(a.right, b.right). The && short-circuits on the first mismatch.
  3. Complexity: O(n) time, where n is the number of nodes visited before a mismatch (at most the smaller tree). Space is O(h) for recursion depth: O(log n) when balanced, O(n) for a skewed, list-shaped tree.
  4. Deep skewed trees can overflow the JavaScript call stack, so I would also write an iterative version that pushes [a, b] pairs onto an explicit stack or queue and applies the same null and value checks to each pair.
  5. Edge cases to test: both trees empty, one tree empty, equal values with different shapes (a lone left child versus a lone right child), and duplicate values.

Follow-up

  • How would you check whether one tree is a subtree of another, and can you beat O(m*n), for example by serialising with null markers and string matching?
  • Instead of a boolean, return the path to the first node where the trees differ, or a full list of added, removed and changed nodes.
  • How would the check change for mirror equality, where left is compared with right?

Write a program to parse a legacy data format, refactor the broken logic, and output a clean JSON structure.

Medium · Coding & Algorithms

Approach

  1. Before changing anything, pin the current behaviour. I run the existing code on sample inputs and save its output as characterization tests, so I know which outputs were bugs and which were behaviour that callers depend on.
  2. Ask what the format is: fixed-width columns, delimited records, or segment-based like HL7 v2, where segments are split by carriage returns, fields by | and components by ^. Ask for the encoding and how empty fields differ from missing ones.
  3. Split the code into stages: tokenize, parse into typed records, validate, then serialize. Each stage is a small pure function with a TypeScript type for its output, which is easier to test and fix than one long loop.
  4. Return errors per record, with the line number and the reason, instead of throwing on the first bad line. A Result type such as { ok: true; value } | { ok: false; error } keeps the good records flowing.
  5. Normalise while mapping: dates to ISO 8601, trimmed strings, numbers parsed explicitly. Note what JSON.stringify does: it drops undefined properties, turns NaN into null and calls Date.toJSON. Parsing is O(n) in input size.

Follow-up

  • The file is too large to fit in memory. How do you stream it line by line (for example with Node's readline) and write newline-delimited JSON?
  • How do you show your refactor did not change any output that callers depend on?
  • A new version of the format adds a field halfway through each record. How does your parser handle both versions?

Build a simple game engine or logic flow (e.g., Tic-Tac-Toe or a grid-based movement game) while explaining yo

Medium · Coding & Algorithms

Build a simple game engine or logic flow (e.g., Tic-Tac-Toe or a grid-based movement game) while explaining your state management choices.

Approach

  1. Model the state explicitly: the board as an array of n*n cells (null, 'X' or 'O'), the current player, and a status of in_progress, won or draw. Keep the engine separate from rendering and input handling.
  2. Make moves a pure function, applyMove(state, move) => newState, that rejects invalid moves: out of bounds, an occupied cell, the wrong player's turn, or any move after the game has ended. Immutable state makes undo, replay and unit tests simple.
  3. For win detection, check only the row, column and diagonals through the last move. That is O(n) per move instead of rescanning the board. For O(1), keep running sums per row, column and diagonal (+1 for X, -1 for O) and check whether any sum reaches n in absolute value.
  4. Detect a draw with a move counter that reaches n*n with no winner. For a grid movement game, the same structure applies: a position, the grid bounds, obstacles in a Set of 'r,c' keys, and a step function that validates each command.
  5. Explain each state-management choice and its trade-off. Storing a history of moves is cheaper than storing full board snapshots, but undoing a move then means replaying or reversing it.

Follow-up

  • Generalise to an n x n board where k in a row wins. What changes in your win check?
  • Add undo and redo. Do you store states or moves, and why?
  • Add a computer opponent. How would minimax with memoisation work here, and how many board states can it visit (at most 3^9 = 19,683 for 3x3)?

Explain the Big O time and space complexity of an algorithm you recently wrote in your professional work.

Medium · Coding & Algorithms

Approach

  1. Pick an algorithm you actually wrote, not a textbook one, and describe it in two sentences: its input, its output, and why it existed.
  2. Name the variables before giving the bound, for example n records and k distinct statuses. Then give time and auxiliary space separately, with the step that dominates each.
  3. Point out costs hidden in library calls: Array.prototype.includes or indexOf inside a loop makes the loop O(n^2), while switching to a Set or Map gives O(1) average lookups. Array.prototype.sort is O(n log n) and has been required to be stable since ES2019.
  4. Separate average from worst case and from amortized cost. Hash maps are O(1) on average but not in the worst case, and array push is amortized O(1). Also mention I/O that Big O hides, such as a database query inside a loop (the N+1 pattern).
  5. Close with evidence: the real size of n, what I measured before and after a change, and whether the asymptotic improvement mattered at that size.

Follow-up

  • What input would trigger the worst case, and how likely is it in production?
  • Could you trade memory for time here, and would that be worth it at your real data size?
  • How would the complexity change if the data had to be paged from the database instead of held in memory?

Given a stream of patient notification logs, write a function to filter and aggregate the events by status wit

Medium · Coding & Algorithms

Given a stream of patient notification logs, write a function to filter and aggregate the events by status within a specific time window.

Approach

  1. Clarify the event shape ({ messageId, status, timestamp, ... }), whether the window is [start, end) or inclusive, the timestamp unit and time zone (I store UTC epoch milliseconds), and whether events arrive in time order. Also ask whether retries produce duplicate events.
  2. Clarify what "aggregate by status" means: a count of events per status, or a count of messages by their latest status. The second needs a Map<messageId, latestEvent> before counting.
  3. Batch version: one pass that filters on the window and increments a Map<status, number>. That is O(n) time and O(k) space for k statuses; with deduplication, add a Set of event ids that are in the window.
  4. Sliding window over a live stream: keep in-window events in a queue ordered by time, and update the counts as each event arrives. Evict from the front while the oldest timestamp falls outside the window, decrementing its status. Each event is enqueued and dequeued once, so the cost is amortized O(1) per event.
  5. Edge cases: an empty stream, events exactly on a window boundary, unknown statuses, and late or out-of-order events. For those I would use fixed time buckets (for example per minute) plus an allowed-lateness cutoff.

Follow-up

  • Events can arrive up to a few minutes late. How do you keep the counts correct?
  • Return counts per status for every 5-minute bucket over the last day, instead of one window.
  • If the logs live in PostgreSQL, write the query: filter on the time range, GROUP BY status and a date_trunc bucket, and say which index helps.

Explain how you would structure a database schema to support multi-tenant organizations with complex hierarchi

Medium · System Design & Architecture

Explain how you would structure a database schema to support multi-tenant organizations with complex hierarchical permissions.

Approach

  1. State the tenant isolation choice and its trade-offs. One shared schema with tenant_id on every table is cheapest to run; schema-per-tenant or database-per-tenant isolate better but make migrations and connection counts harder. With a shared schema, PostgreSQL row-level security can enforce tenant_id = current_setting('app.tenant_id').
  2. Use composite keys so data cannot cross tenants. Every index starts with tenant_id, and foreign keys reference (tenant_id, id), so a row cannot point at another tenant's row.
  3. Model the hierarchy (for example organization, facility, department) as an adjacency list with parent_id, read with WITH RECURSIVE. For frequent 'all descendants' checks, add a closure table (ancestor_id, descendant_id, depth) or an ltree path with a GiST index.
  4. Permissions: roles, role_permissions and assignments(user_id, role_id, node_id). An assignment applies to the node and all its descendants. A check asks whether the user holds an assignment granting the permission on the target node or any of its ancestors, which is one indexed join with a closure table.
  5. Cache each user's effective permissions and invalidate the cache when assignments or the tree change. Decide early whether explicit denials exist, because deny-overrides-allow complicates every check.

Follow-up

  • A facility moves to a different parent. What does that cost with a closure table, compared with an adjacency list?
  • How do you answer an audit question such as 'who could read this patient's record last March'?
  • One tenant grows a hundred times larger than the rest. What do you change?

Stop tag and share joins from fanning out a page

Medium · join fan-out · semi-joins · many-to-many

resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.

Approach

  1. Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
  2. Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
  3. Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
  4. Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.

Follow-up

  • The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
  • A resource can be shared with the same user twice under different permissions. Does your count change, and should it?

Design a high-throughput notification dispatch system that can send SMS, email, and push notifications to mill

Medium · System Design & Architecture

Design a high-throughput notification dispatch system that can send SMS, email, and push notifications to millions of patients daily.

Approach

  1. Size the load first. 10 million notifications a day is about 116 per second on average (10,000,000 / 86,400), but scheduled reminders cluster in peaks, so I design for the peak rate and for provider limits, not the daily average.
  2. Write path: an API accepts a send request with an idempotency key, stores a notification row (status queued) in PostgreSQL, and publishes to a separate queue per channel (SMS, email, push) on a broker such as Kafka or RabbitMQ. That way a slow channel cannot block the others.
  3. Channel workers check opt-outs, preferences and quiet hours in the patient's time zone, then call the provider (an SMS gateway, an email service, APNs or FCM). Each provider gets a token-bucket rate limiter, retries with exponential backoff and jitter, and a dead-letter queue.
  4. Delivery is at least once, so workers deduplicate on the idempotency key before calling a provider. Provider delivery webhooks move the notification through a state machine: queued, sent, delivered, failed.
  5. For scheduled sends, store a send_at timestamp and have the scheduler claim due rows with SELECT ... FOR UPDATE SKIP LOCKED on an index over (status, send_at). For health data, keep message bodies minimal, encrypt at rest and keep an audit log.

Follow-up

  • The SMS provider is down for an hour. What happens to the queue, and when do you fail over to a second provider?
  • How do you stop one large tenant's campaign from delaying another tenant's urgent reminders?
  • Can you guarantee exactly-once delivery to the patient? If not, what is the closest you can get?

How would you architect a synchronization service between Artera and an external Electronic Health Record (EHR

Medium · System Design & Architecture

How would you architect a synchronization service between Artera and an external Electronic Health Record (EHR) database that has strict API rate limits?

Approach

  1. Clarify the sync: direction (pull, push or both), required freshness, the rate limit's unit (per minute, per credential, per tenant), and whether the EHR offers change notifications such as FHIR Subscriptions or only polling. Ask about a bulk export for the initial backfill (FHIR Bulk Data $export).
  2. Put every call behind one shared rate limiter per EHR credential, such as a token bucket in Redis. Then all workers spend the same budget, and a priority queue lets user-triggered writes go ahead of background backfill.
  3. Sync incrementally with a high-water mark (for example the FHIR _lastUpdated search parameter). Persist the cursor only after a page has been applied, so a crash replays a page instead of skipping it.
  4. On HTTP 429, honour Retry-After; otherwise use exponential backoff with jitter. Add a circuit breaker so an EHR outage does not burn the budget. Writes are idempotent upserts keyed by the external resource id and its version (FHIR meta.versionId).
  5. For outbound writes, use the outbox pattern: commit the change and an outbox row in one transaction, and let a relay send it to the EHR. A reconciliation job compares records periodically, because polling on updated timestamps never sees deletions.

Follow-up

  • The EHR is unavailable for six hours. How does the backlog drain afterwards without breaching the rate limit?
  • How do you detect records deleted or merged in the EHR?
  • Several tenants share one EHR vendor's limit. How do you split the budget fairly?

Design a real-time chat system that allows clinical staff to communicate directly with patients, ensuring low

Medium · System Design & Architecture

Design a real-time chat system that allows clinical staff to communicate directly with patients, ensuring low latency and message persistence.

Approach

  1. Clarify scope: one-to-one or group conversations, whether patients chat in a web app or over SMS, the number of concurrent connections, and the history and retention requirements.
  2. Connection layer: WebSocket gateways hold client connections, and a registry maps each user to their gateway node. A pub/sub channel such as Redis pub/sub fans messages out across nodes.
  3. Persist before acknowledging: the message service writes the message to the database, assigns a per-conversation sequence number, then publishes. The client sends a client-generated message id so a retry after a timeout cannot create a duplicate.
  4. Redis pub/sub is fire-and-forget, so the database stays the source of truth. On reconnect the client sends its last sequence number and the server replays the gap. Offline users get a push or SMS notification.
  5. Storage: a messages table indexed on (conversation_id, seq), with keyset pagination for history. Authorization is checked per conversation on every send and subscribe; data is encrypted in transit and at rest, and access to health information is audit-logged. Typing indicators and presence stay ephemeral.

Follow-up

  • Several clinic staff share one patient conversation. How do you handle assignment and read receipts?
  • A gateway node crashes while a message is in flight. Walk through what the sender and the recipient see.
  • How would you add search over message history without slowing writes?

Describe your approach to migrating a monolithic service to a microservices architecture without causing downt

Medium · System Design & Architecture

Describe your approach to migrating a monolithic service to a microservices architecture without causing downtime for active clients.

Approach

  1. Use the strangler fig pattern. Put a routing layer (reverse proxy or API gateway) in front of the monolith, then move one route or bounded context at a time while everything else keeps going to the monolith.
  2. Choose the first extraction by its seams: a context with few inbound calls and data it clearly owns. Inside the monolith, first put that code behind an interface, so the switch becomes a call to a different implementation.
  3. Data is the hard part. The new service owns its tables, so I backfill them and keep them current with change data capture or an outbox, not ad-hoc dual writes that can diverge. For schema changes, I use expand and contract: add, dual-write, backfill, switch reads, then drop.
  4. De-risk the cutover with shadow traffic that compares old and new responses, then a percentage rollout behind a feature flag, with per-route error-rate and latency dashboards and a one-switch rollback.
  5. Name what the split adds: network failures between services, the loss of single-database transactions (replaced by sagas with compensating actions), and more to deploy and monitor. Splitting is worth it only where independent scaling or deployment pays for that.

Follow-up

  • An operation used to be one database transaction and now spans two services. How do you keep it consistent?
  • After the cutover, writes have already gone to the new service and you need to roll back. What now?
  • How do you decide that a part of the monolith should stay where it is?

How do you manage your time and stay productive when working in a highly distributed, remote-first team enviro

Medium · Behavioral & Team Fit

How do you manage your time and stay productive when working in a highly distributed, remote-first team environment?

Approach

  1. Answer with one concrete example, not a list of tools. Describe a stretch of remote work, what was hard about it (time zones, waiting on reviews, unclear priorities), and the habits you used.
  2. Show how you plan: weekly priorities agreed with your lead, focus blocks for deep work, and work split into reviewable pieces so progress stays visible in tickets and pull requests.
  3. Show written communication that unblocks people: design notes and pull request descriptions that answer the obvious questions, end-of-day handoff notes when teammates are in other time zones, and blockers raised early in a shared channel instead of waited out.
  4. Explain when you switch from writing to a call, for example after a thread has gone back and forth twice. Then record the decision in writing afterwards.
  5. The mistake that sinks this answer is describing only your own productivity. Include how you avoided becoming a blocker, for example by reviewing teammates' pull requests before starting your own work.

Follow-up

  • Tell me about a time asynchronous communication failed on your team. What did you change afterwards?
  • How do you get unblocked when the only person who can answer is asleep in another time zone?
  • How did you onboard, or help onboard, someone you never met in person?

Listing latency scales with page size, not with filters

Easy · n+1 · latency · query batching

The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.

Approach

  1. Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
  2. Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
  3. Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
  4. Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.

Follow-up

  • The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
  • How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?

Suggested practice checklist

Each day is built from the reported rounds, the fifteen reported questions and the listed stack (TypeScript, PostgreSQL, REST). Coding comes first, then design, then stories and a full run-through.

Day 1 — Map the loop and the stack

  • Write the four reported rounds in order and note for each what you can prepare: recruiter screen, introductory conversation, technical evaluation, virtual on-site loop.
  • List the stack in the role description (TypeScript or JavaScript, PostgreSQL, REST) and the nice-to-have items (HL7 or FHIR, RabbitMQ or Kafka, AWS), and mark each as strong, rusty or new.
  • Skim the fifteen reported questions and rank them by how unready you feel.

Deliverable: A one-page loop map plus a ranked list of the weakest stack items and questions, which sets the order of the remaining days.

Day 2 — TypeScript trees and complexity talk

  • Write a function comparing two binary trees for structural identity in TypeScript with a typed node whose children can be null. Do the recursive version, then an iterative one with an explicit stack of node pairs; state O(n) time and O(h) space.
  • Add a variant that also compares node values, and test both-null, one-null, skewed and single-node trees.
  • Pick an algorithm from your own recent work and write a two-sentence time and space analysis, naming what dominates at production input sizes, then say it aloud.

Deliverable: Two tested tree-comparison implementations and a spoken complexity explanation of your own code.

Day 3 — Parsing and windowed aggregation

  • Invent a legacy format with quirks (fixed-width or delimited rows, missing fields, inconsistent date formats) and write a TypeScript parser that returns validated records plus a list of rejected rows, then emits clean JSON.
  • Refactor your first version into small functions: tokenise, validate, normalise, serialise, and add a few tests for the broken inputs.
  • Write a function that takes notification log events (id, channel, status, timestamp), filters to a half-open time window and returns counts by status in a Map; make it work in a single pass over an iterable and cover unsorted, empty and unknown-status input.

Deliverable: A parser with a rejects list and tests, plus a single-pass windowed aggregator with its edge cases written out.

Day 4 — State management and the rate limiter

  • Build Tic-Tac-Toe as a pure reducer: state holds board, next player and winner, and an action places a mark. Reject moves on occupied cells or after a win, and detect wins and draws.
  • Extend it to a grid-movement game with bounds and obstacles, and explain why you kept state immutable and where the transitions live.
  • Write a rolling-window rate limiter class with a per-user queue of timestamps, eviction of expired entries and an injectable clock; state amortised O(1) per request and describe a fixed-window counter alternative.

Deliverable: A tested game reducer and a rate limiter, each with a one-paragraph explanation of the state-management choice.

Day 5 — Dispatch and EHR sync designs

  • Diagram the notification dispatch system: ingest API, durable queue, per-channel workers for SMS, email and push, provider adapters, status store in PostgreSQL, scheduled sends, idempotency key per message and channel, backoff with jitter, and a dead-letter queue.
  • Add per-tenant fairness and describe what happens when one provider is down for an hour (circuit breaker, requeue, alert).
  • Diagram the EHR synchronisation service: a shared token bucket matching the API limit, incremental fetch with an updated-since cursor, coalescing of duplicate updates, honouring Retry-After on 429s, and a periodic reconciliation job.

Deliverable: Two labelled architecture diagrams, each with a written list of failure modes and how the design handles them.

Day 6 — Chat, tenancy schema and migration

  • Design the clinician-to-patient chat: persistent connections through a gateway, write the message to durable storage before acknowledging, per-conversation sequence numbers for ordering, pub/sub fan-out and a reconnect path using a last-seen cursor.
  • Write the PostgreSQL schema for multi-tenant organisations with hierarchical permissions: tenant_id on every table, a closure table or ltree path for the hierarchy, role grants at node level with inheritance, and indexes that lead with tenant_id; note where row-level security fits.
  • Outline a zero-downtime monolith-to-services migration: strangler routing at a gateway, shadow traffic, expand-and-contract schema changes, and a rollback step per stage.

Deliverable: A chat design, a tenancy schema with its indexes, and a staged migration plan with rollbacks.

Day 7 — Stories and a full mock

  • Write bullet outlines for the legacy-code, technical-disagreement, stakeholder-explanation and fast-learning stories, each with a decision of yours, a trade-off and a result.
  • Write a short answer to how you manage your time and stay productive in a distributed, remote-first team, covering written updates, async handoffs and how you ask for help.
  • Run a mock of one coding question and one design question aloud in a shared editor, then tell two of your stories in both a short and a detailed version.

Deliverable: Five story outlines (including the remote-work answer) and notes from one spoken mock covering coding, design and behavioral answers.

Stories to rehearse for Artera

Candidates report behavioral questions on legacy code, disagreements with senior engineers or product managers, explaining hard problems to non-technical people, working remotely and learning quickly. They also describe scenario prompts about inheriting an undocumented service and compromising on implementation to meet a business deadline. Prepare four or five stories in which you made the decisions, then check that each can answer more than one of these prompts.

  1. How do you manage your time and stay productive when working in a highly distributed, remote-first team environment?
  2. Describe a time when you inherited a poorly documented service. How did you go about understanding and stabilizing it?

Describe a time when you had to work with a legacy codebase. How did you balance delivering new features with

Medium · Behavioral & Team Fit

Describe a time when you had to work with a legacy codebase. How did you balance delivering new features with refactoring existing code?

Approach

  1. Choose a codebase you worked in for a meaningful stretch and a feature you personally delivered in it. Give the context in two sentences: what the system did, why it was hard to change, and what the deadline pressure was.
  2. Show the balancing rule you used rather than saying you balanced it. Examples: refactor only the code paths the new feature touches, add characterization tests that pin current behaviour before changing it, and put the work behind a feature flag so it can roll back.
  3. Explain how you decided what not to clean up. Name something you left alone, and why that was the right call given risk and value.
  4. Quantify the effect where you can: fewer incidents, shorter lead time for later changes, test coverage on the touched module, or review time. Say what you measured and over what period.
  5. Describe how you kept the product manager informed, for example by splitting refactoring into small reviewable pull requests with each one tied to a delivered feature.

Follow-up

  • When would you argue for rewriting a module instead of refactoring it incrementally?
  • How did you make changes safely when the code had no tests?
  • What would you do differently if the deadline had been half as long?

Talk about a situation where you had a technical disagreement with a senior engineer or product manager. How d

Medium · Behavioral & Team Fit

Talk about a situation where you had a technical disagreement with a senior engineer or product manager. How did you resolve it?

Approach

  1. Pick a real disagreement about a technical decision, such as a data model, a build-versus-buy choice, a migration approach or scope, where the other person had more authority or context than you. The story must show you took a position and acted on it.
  2. State their position fairly in one or two sentences before yours. A story where the other person was simply wrong, or where you only complained, sinks the answer.
  3. Show how the disagreement moved from opinion to evidence: a small prototype, a benchmark, a written comparison of options, or an agreement on what is reversible and what is not.
  4. Say how it ended, including the cases where you were overruled or turned out to be wrong. 'I raised my concern, we agreed on the criteria, and I committed to the decision' is a legitimate outcome if you then supported it.
  5. Close with what the relationship looked like afterwards and the habit you adopted, such as writing a one-page decision record before a design debate.

Follow-up

  • What if they had still disagreed after you showed the data?
  • Tell me about a time you were wrong in a disagreement like this.
  • How do you handle a disagreement with a product manager when the date is fixed?

Tell me about a complex technical problem you solved recently. How did you explain the solution to non-technic

Medium · Behavioral & Team Fit

Tell me about a complex technical problem you solved recently. How did you explain the solution to non-technical stakeholders?

Approach

  1. Choose a problem where the technical cause was non-obvious, such as a duplicate-delivery bug, a slow query, a failed migration or an unreliable integration. Spend a few sentences on the technical diagnosis, then the bulk of the answer on the explanation.
  2. Show that you translated the problem into the stakeholder's terms: user or customer impact, risk, cost and the options they could choose between. For a messaging system, that might be 'some reminders arrived twice' rather than 'the consumer was not idempotent'.
  3. Name the concrete communication tool: a one-page summary, a simple diagram, an analogy you tested, or a before-and-after number. Describe what you left out deliberately.
  4. Show how you checked that they understood, for example by asking them to restate the decision they were being asked to make, and what changed as a result of their feedback.
  5. Finish with the result: the decision they made, the fix, and how you reported progress afterwards.

Follow-up

  • What did the stakeholder push back on, and how did you respond?
  • How did you decide how much technical detail to leave out?
  • How would you explain the same problem to an engineer on another team?

Describe a situation where you had to quickly learn a new technology or domain to complete a critical project.

Medium · Behavioral & Team Fit

Approach

  1. Pick a case where you had a hard deadline and little prior knowledge: a new language, framework, infrastructure service, or a domain with its own rules and vocabulary. If you have not worked in healthcare, a domain like payments or logistics works to show the method you would reuse for EHR standards such as HL7 or FHIR.
  2. Describe your learning method as steps: define the minimum you needed to be effective, read the existing code or specification, find the person who knows it and ask specific questions, build a small end-to-end slice, and get it reviewed early.
  3. Say how you reduced the risk of being wrong while still new: tests against real examples, review from someone experienced, a staged rollout, or writing down your assumptions and having them checked.
  4. Give the outcome and what you left behind: the project shipped, and you wrote a short guide, a test suite or documentation so the next person ramped up faster.
  5. Be honest about the mistake you made along the way and what it taught you, since a story with no stumbles reads as untested.

Follow-up

  • How did you decide you knew enough to start building?
  • How did you check that you had not misunderstood a domain rule?
  • How would you approach getting up to speed on an EHR integration you had never seen?