cswork / libraryOpen practice workspace

Interview guides · AVIA

AVIA Software Engineer Interview Guide

AVIA Software Engineer interview guide: three reported rounds, coding, system design and behavioral questions, with approaches and a 7-day plan.

The 60-second brief

Reports for this loop describe AVIA only in general terms. Its engineers build the platforms behind the company's products: backend services, user-facing features and microservices that handle large data volumes under security and performance requirements. No specific product line is named, so this guide does not guess at one. Candidates describe the Software Engineer working alongside product managers, design and QA, and owning a feature from technical scoping through monitoring after release. Experience with healthcare technology or regulated data is listed as a plus rather than a requirement.

This guide is for Software Engineer candidates preparing for AVIA's reported three-round loop. Candidates describe a typical requirement of 3 or more years of professional experience, varying with the seniority of the opening. It covers the coding and concurrency fundamentals reported for the first stage, the behavioral questions reported for the HR and project manager stage, and the system design scenarios reported for the CTO whiteboard. It does not cover compensation or offer negotiation.

Candidates report three rounds over roughly 3 to 5 weeks: a Technical Screening, a Collaborative Round with HR and a project manager, and an Executive Whiteboard Session with the CTO.

Focus areas

  • Whiteboard Problem Solving
  • Technical Interview (Core Skills)
  • Coding/Algorithmic Thinking

Overview

The three reported stages test three different skills, so a single study mode will not cover them. The first stage is working code and computer science fundamentals. The second is a structured conversation about collaboration, deadlines and feedback. The third is one extended design problem at a whiteboard. Split your preparation the same way, and give the weakest stage your first sessions.

For the technical screen, the reported questions are classic and bounded: find two numbers that sum to a target, reverse a linked list in place, find the longest substring without repeating characters, explain a process versus a thread, and handle deadlocks. Reported scenarios add a top-K most frequent actions function, an LRU cache with an eviction policy, and speeding up a search that times out on large inputs. The requirements candidates describe list Python, Java, JavaScript/TypeScript or Go, so pick one and get fluent enough to write running code with its standard hash map, deque, heap and lock types.

The whiteboard stage draws on a wider spread of design prompts: a URL shortener and its database choices, a distributed cache, a highly available chat application, database failover with no data loss, an ingestion pipeline for millions of analytical events per minute, a real-time notification system, traffic spikes during a major event, and a data model with API endpoints for progress tracked across devices. Reported topics include data modeling for specific query patterns, sharding, load balancing and secure service-to-service communication. Build one reusable skeleton (requirements, API, data model, scaling, failure handling) rather than memorising six architectures.

The Collaborative Round leans on stories from the work the role describes: code reviews, sprint planning with product managers, refactoring and technical debt, and mentoring. Candidates report the STAR structure as the expected format and recommend at least three versatile stories. Prepare stories where your own actions are clear, because the reported prompts ask about pivots, conflict, initiative, prioritisation and feedback.

The AVIA interview process

Candidates report three rounds in this order: a technical screen, a collaborative round with HR and a project manager, and a CTO whiteboard session. Pacing is described as roughly 3 to 5 weeks overall and can shift with team availability.

Technical Screening

Candidates describe this stage as covering core computer science fundamentals and practical coding. Reported topic areas are arrays, hash maps, linked lists and trees; sorting, searching and basic dynamic programming; spotting the bottleneck in a brute-force answer and fixing it; and language details such as memory management and concurrency. Reported scenarios include returning the top K most frequent actions from user-interaction data, implementing an LRU cache, and speeding up a search function that times out on large inputs. This is where the coding questions (two-sum, reversing a linked list, longest substring) and the process-versus-thread and deadlock questions are practised. Approach it by choosing one language, writing code that actually runs, stating the brute-force version and its complexity first, and then improving it.

What to demonstrate

  • Working, bug-free code in your chosen language, with time and space complexity stated
  • Choosing between arrays, hash maps, linked lists and trees from the access pattern
  • Moving from a brute-force answer to an optimised one and naming the trade-offs
  • Language-level knowledge such as memory management and concurrency

How to prepare

  • Write the two-sum, linked-list reversal and longest-substring solutions from a blank file, then list the edge cases each one needs (empty input, single element, duplicates, repeated characters)
  • Implement an LRU cache and a top-K frequent items function in your chosen language, and say aloud what each operation costs
  • Explain a process versus a thread, then describe a deadlock and how you would prevent and diagnose it, using concrete lock types from your language

Collaborative Round

Candidates describe this as a conversation with HR and a project manager, centred on cultural fit and communication. Reported themes are resolving conflict with technical peers or product managers, delivering under tight deadlines and shifting requirements, managing technical debt, mentoring, and explaining technical blockers to non-technical audiences. Reported example prompts include disagreeing with a product manager on a requirement, compromising on code quality to meet a deadline, and making sure technical ideas land with cross-functional teammates. Candidates are advised to answer in STAR form and to keep at least three adaptable stories ready. Approach it by choosing stories where your own decision is visible and the outcome is concrete.

What to demonstrate

  • Communication with non-technical stakeholders, as reported for this stage
  • How you handle disagreement, shifting requirements and deadline pressure
  • Whether your own contribution, rather than the team's, is clear in each story
  • Fit with a cross-functional team made up of product managers, design and QA

How to prepare

  • Write six stories in STAR form covering a pivot, a difficult teammate, a self-started improvement, competing deadlines, critical feedback, and a disagreement with a product manager
  • For each story, mark the sentence that states what you personally decided or did, and cut team-level description that hides it
  • Rehearse one answer to the code-quality-versus-deadline prompt that names what you cut, what you protected, and how you recorded the debt
  • Practise explaining a technical blocker (for example a slow query or an unreliable dependency) in terms of user or schedule impact

Executive Whiteboard Session

Candidates describe this as a single, extended whiteboard session with the CTO, focused on system design and architectural thinking rather than quick coding questions. Reported topics are scalable, fault-tolerant backend design, data modeling around specific query patterns, sharding and load balancing, and secure communication between microservices. Reported scenarios include a real-time notification system, handling traffic spikes during a major event, and a data model plus API endpoints for tracking progress across devices. The design questions in this guide (URL shortener, distributed cache, chat, database failover, event ingestion) are the practice material for it. Approach it by clarifying traffic, consistency and failure requirements before drawing, narrating your assumptions, and treating pushback on a choice as a trade-off to discuss.

What to demonstrate

  • System architecture: scalable, fault-tolerant backend components and how they connect
  • Data modeling that matches the read and write patterns you were given
  • Scaling decisions such as sharding, replication and load balancing, with reasons
  • Communicating reasoning aloud and revising a design when a constraint changes

How to prepare

  • Build a repeatable outline for any design prompt: requirements and traffic, API, data model, read and write paths, scaling, failure handling, security
  • Whiteboard the URL shortener and the failover question end to end, stating the consistency and data-loss guarantee each choice gives
  • Sketch the notification system and the event ingestion pipeline, naming where a queue absorbs bursts and what happens when a consumer falls behind
  • Practise changing one requirement mid-design (traffic ten times higher, a database down) and saying what changes and what does not

Where candidates lose points

Stopping at the nested-loop answer to two-sum or longest substring, or never stating complexity

Say the brute force and its cost in one sentence, then improve it. Two-sum becomes a single pass with a hash map of value to index. Longest substring becomes a sliding window with a last-seen index per character. Test inputs such as an empty string, one repeated character and a string where an old duplicate sits before the window start.

Losing the rest of the list while reversing it in place, or failing on empty and single-node lists

Keep three pointers (previous, current, next), save next before you overwrite it, and return previous at the end. Run the empty, one-node and two-node cases by hand before saying you are done, and be ready to write the recursive form and explain its stack depth.

Answering process versus thread and deadlock questions with textbook definitions and no concrete mechanism

Tie each answer to something you could demonstrate: separate address spaces versus shared heap, what a context switch costs, and a two-lock deadlock with the fix (consistent lock ordering or a timeout). Name the real lock and dump tools of your chosen language.

Drawing boxes immediately in the whiteboard round and promising zero data loss or high availability without the cost

Ask about traffic, read and write ratio and consistency needs first. Then state guarantees with their price: zero data loss on failover means a replica must acknowledge writes before commit, which adds latency and can reduce availability. Say where you accept stale data, for example in a cache.

Behavioral answers told in "we", with no stated decision of your own and no measurable result

For each story, write one sentence beginning "I decided" or "I did" and one result you can defend. Keep the situation to two sentences, and stay with your reasoning when a follow-up pushes back instead of retreating to the team's outcome.

Questions & practice

Given an array of integers, return the indices of the two numbers that add up to a specific target.

Medium · Technical and Coding Fundamentals

Approach

  1. Brute force checks every pair in O(n^2). I improve it with one pass and a hash map from value to index: for each element I compute target - x, return both indices if the complement is already in the map, and otherwise store x with its index. That is O(n) time and O(n) extra space.
  2. I look up the complement before inserting the current value. That stops an element pairing with itself (target 6 with a single 3), and duplicates such as [3, 3] still work because the first 3 is already stored when the second one arrives.
  3. Alternative: sort (value, index) pairs and move two pointers inward from both ends. That is O(n log n) time. Sorting plain values would lose the original indices, so it is only worth it when the input is already sorted, where two pointers need O(1) extra space.
  4. Before coding I ask whether exactly one answer is guaranteed, what to return when there is none (an empty result or an exception) and whether values can be negative. In Java I compute the complement as a long so target - x cannot overflow.

Follow-up

  • How would you return every unique pair that sums to the target, without repeating pairs when the input has duplicates?
  • If the array is already sorted, can you solve it in O(1) extra space?
  • How does this extend to three numbers that sum to the target, and what does that cost?

Write a function to reverse a linked list in place.

Medium · Technical and Coding Fundamentals

Approach

  1. Iterative version with three references: prev = null, curr = head. On each step I save next = curr.next, point curr.next at prev, then move prev to curr and curr to next. When curr is null, prev is the new head. O(n) time, O(1) extra space.
  2. The usual bugs are overwriting curr.next before saving it and returning the old head. I trace a three-node list by hand before I run anything.
  3. Recursive version: reverse the rest of the list, then set head.next.next = head and head.next = null. It is O(n) time but also O(n) call stack, so a long list can overflow the stack (CPython's default recursion limit is 1000). I would use the iterative version in production.
  4. Edge cases I test: an empty list, a single node and two nodes. I also check that the old head's next ends up null so the result has no cycle.

Follow-up

  • Reverse only the nodes between positions m and n and leave the rest of the list in place.
  • Reverse the list in groups of k nodes, leaving a final group shorter than k unchanged.
  • How would you use a reversal to check whether a linked list is a palindrome in O(1) extra space?

How would you find the longest substring without repeating characters?

Medium · Technical and Coding Fundamentals

Approach

  1. Brute force checks every substring for duplicates, which is O(n^3), or O(n^2) if I grow each start position with a set. A sliding window brings it down to O(n).
  2. Window with a map from character to last index: for each right position, if the character was last seen at an index >= left, I move left to that index + 1. Then I record the character's new index and update best = max(best, right - left + 1).
  3. The >= left check matters. Without it a stale index from before the window can move left backwards. On "abba", when the last 'a' arrives its old index 0 is already outside the window [2, 3], so left must stay at 2.
  4. Set-based variant: while s[right] is in the set, remove s[left] and advance left. Each character enters and leaves the set at most once, so it is still O(n). Space is O(min(n, alphabet size)).
  5. Edge cases: an empty string returns 0, a string of one repeated character returns 1 and an all-distinct string returns n. I confirm case sensitivity and the character set first, and I track the start index too in case the actual substring is wanted.

Follow-up

  • Find the longest substring with at most k distinct characters instead.
  • Return the substring itself, not just its length. What changes if there are ties?
  • How would you handle characters arriving as a stream that you cannot store in full?

Explain the difference between a process and a thread.

Medium · Technical and Coding Fundamentals

Approach

  1. A process is an instance of a program with its own virtual address space and OS resources such as the file-descriptor table. A thread is a unit of scheduling inside a process. Threads in the same process share the heap, globals, code and open files, while each thread has its own stack, registers, program counter and thread-local storage.
  2. Isolation: one process cannot corrupt another's memory, and a crash usually stays inside the process. A thread that corrupts memory or crashes can take down the whole process.
  3. Cost: creating a process is heavier (fork uses copy-on-write but still duplicates page tables), and switching between processes changes the address space, which can mean TLB flushes. Threads are cheaper to create and switch between.
  4. Communication: threads share memory directly, so access needs mutexes, atomics or other synchronization to avoid data races. Processes communicate through IPC: pipes, sockets, shared-memory segments or message queues.
  5. Language detail: CPython's GIL stops threads running Python bytecode in parallel, so CPU-bound Python work uses multiprocessing, while I/O-bound work is fine on threads or asyncio. Java platform threads map to OS threads, and Go multiplexes goroutines onto a small pool of OS threads.

Follow-up

  • What happens when a multithreaded process calls fork? Only the calling thread is copied, so a lock held by another thread can stay locked in the child.
  • How do coroutines or green threads differ from OS threads, and when would you pick them?
  • For a CPU-heavy job in your main language, would you use threads or processes, and why?

Replace offset paging on the resource feed with keyset

Easy · keyset pagination · composite index · sort order

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

Approach

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

Follow-up

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

Explain why the owner filter ignores the listing index

Medium · composite index · query plans · sort elimination

The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.

Approach

  1. Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
  2. For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
  3. Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
  4. Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.

Follow-up

  • 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
  • A dashboard runs this for 40 owners in one page load. What changes about the design?

Design a URL shortening service like Bitly. What are the database considerations?

Medium · System Design and Whiteboarding

Approach

  1. Scope: a write path (POST a long URL, get back a short code) and a read path (GET the code, receive a redirect). I assume reads far outnumber writes, then ask about custom aliases, expiry and click analytics. The redirect path is the one that has to scale.
  2. Key generation: base62-encode a unique numeric ID. A counter service hands out ID ranges to app servers so they never coordinate per request. Seven base62 characters give 62^7, about 3.5 trillion codes. Other options are a truncated hash of the URL with a collision check, or random codes inserted under a unique constraint. Sequential IDs are guessable, so I would permute them if that matters.
  3. Database: the access pattern is a lookup by primary key, so a table of (code PK, long_url, owner_id, created_at, expires_at) works in a relational DB or a key-value store. I shard by hash of the code. I add an index on long_url only if the same URL must map to one code. Custom aliases depend on insert-if-absent under a unique constraint to settle races.
  4. Read path: put a cache such as Redis with LRU eviction in front of the store, because popular links are heavily skewed. A 301 lets browsers cache the redirect and cuts load but hides repeat clicks. A 302 sends every click through the service, which analytics needs.
  5. Analytics: I never increment a counter row on every redirect, because popular links turn it into a hot row. I publish click events to a queue and aggregate them asynchronously. Expired links are filtered at read time and removed by a background job.

Follow-up

  • How do you stop the service being used to spread malicious URLs?
  • One link suddenly gets a large share of all traffic. What breaks, and what do you change?
  • How would you guarantee two users never receive the same code when many app servers generate codes at once?

How would you design a distributed cache system?

Medium · System Design and Whiteboarding

Approach

  1. Scope: get/put/delete with TTLs, the latency target, total data size, and how much staleness callers can accept. I also settle the caching pattern: cache-aside, read-through, write-through or write-behind. Write-behind is fastest but can lose writes.
  2. Partitioning: consistent hashing with virtual nodes, so adding or removing a node remaps only about 1/N of the keys instead of nearly all of them, as hash mod N would. Routing happens either in a client library that knows the ring or in a proxy layer.
  3. Node internals: a hash map plus a doubly linked list gives O(1) LRU get and put. I enforce a memory limit with an eviction policy (LRU or LFU) and expire TTLs lazily on read, plus periodic sampling so cold expired keys are cleared.
  4. Failure and replication: each shard gets a primary and replicas. Asynchronous replication means a failover can lose recent writes. That is usually acceptable for a cache, but a failed node sends a burst of misses to the database, so I plan for warm-up.
  5. Hot paths: guard against stampedes with request coalescing (one loader per key), jittered TTLs and early refresh. Handle hot keys with a small in-process cache or by replicating the key. On a database write I delete the cache entry instead of updating it, which narrows the race where an old value overwrites a newer one.

Follow-up

  • How do you keep the cache consistent with the database when two writers race?
  • A node dies during peak traffic. Walk through what happens to the keys it held and to the database behind it.
  • How would you detect and handle a single key that takes a large share of reads?

Walk me through the architecture of a highly available chat application.

Medium · System Design and Whiteboarding

Approach

  1. Scope: one-to-one and group chat, message history, delivery and read receipts, presence, and push notifications for offline users. The hard requirements are no lost messages and correct ordering within a conversation.
  2. Connections: clients hold persistent WebSocket connections to gateway servers behind a load balancer. A session registry (for example in Redis) maps each user and device to its gateway, and heartbeats remove dead connections.
  3. Send path: the client attaches a client-generated message ID, and the chat service writes the message durably before it acks the sender. Each message gets a per-conversation sequence number, and the service fans it out through pub/sub to the recipients' gateways. Offline recipients get a push and fetch everything after their last sequence number when they reconnect.
  4. Storage: a wide-column store partitioned by conversation ID and clustered by message ID fits an append-heavy workload where reads ask for the most recent N messages. Very large groups may need fan-out on read instead of copying to every member.
  5. Availability: gateways are interchangeable. Clients reconnect with exponential backoff and resume from their last acknowledged sequence. Storage is replicated across zones. Delivery is at-least-once, and clients dedupe by message ID so each message is shown once.

Follow-up

  • How do you keep message order consistent for one user on several devices?
  • How would presence work for millions of users without flooding every contact with updates?
  • What changes for a group with tens of thousands of members?

If our main database goes down, how would you design a failover mechanism to ensure zero data loss?

Medium · System Design and Whiteboarding

Approach

  1. First I pin down the targets. Zero data loss means an RPO of zero. I also ask for the RTO and whether the failover is within one region or across regions, because that decides the replication mode.
  2. An RPO of zero needs synchronous replication: a commit is acknowledged only after a standby has durably received the WAL. In PostgreSQL that is synchronous_standby_names with synchronous_commit = on, or remote_apply if reads on the standby must see the commit. The cost is an extra round trip on every write. A quorum such as ANY 1 of two standbys keeps writes flowing when one standby is down.
  3. Detection and promotion: a consensus-backed manager (for example Patroni on etcd) or a managed multi-AZ service decides when the primary is dead. It promotes only a standby that was in sync and fences the old primary so it cannot accept writes. Without fencing, a partition can produce split brain.
  4. Client cutover: apps reach the database through a stable endpoint (a proxy, virtual IP or DNS name) that moves to the new primary. Transactions in flight during the failure have an unknown outcome, so writes carry idempotency keys and are retried safely.
  5. Replication copies bad writes too, so I keep WAL archiving and base backups for point-in-time recovery. I run failover drills on a schedule and measure the actual RPO and RTO.

Follow-up

  • The primary is cut off from the standbys but still reachable by some clients. How do you prevent split brain?
  • Can you keep zero data loss across regions, and what does it do to write latency?
  • How would you prove in a drill that no committed transaction was lost?

Draw a system architecture for a service that ingests and processes millions of analytical events per minute.

Medium · System Design and Whiteboarding

Approach

  1. Sizing first: 5 million events a minute is about 83k per second. At about 1 KB per event that is roughly 83 MB/s before any peak factor. I ask about peak-to-average ratio, event size, the latency the results need (seconds or hours) and how long raw data is kept.
  2. Ingest: stateless collector endpoints behind a load balancer validate events against a schema and append them in batches to a durable partitioned log such as Kafka or Kinesis. The collector acks the client only after the log confirms the write (acks=all in Kafka). Clients batch and retry with backoff.
  3. Partitioning: ordering holds only within a partition, so I pick a key such as user or device ID. The partition count caps consumer parallelism. A skewed key creates a hot partition, which I fix by salting the key or splitting that traffic.
  4. Processing: a stream processor (Flink, Kafka Streams or Spark Structured Streaming) computes windowed aggregates on event time, using watermarks for late events, and writes them to an OLAP store for queries. A separate sink writes raw events to object storage as Parquet partitioned by date and hour, for backfills and reprocessing.
  5. Correctness and operations: delivery is at-least-once end to end, so every event carries an ID and sinks dedupe or upsert. I monitor consumer lag and autoscale on it, and use a schema registry so producers can evolve event formats without breaking consumers.

Follow-up

  • Events arrive hours late from offline devices. How do your aggregates handle them?
  • A bug corrupted a day of aggregates. How do you recompute them?
  • Traffic triples during a major event. What saturates first, and how do you absorb it?

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 covers one slice of the reported loop: coding fundamentals first, then concurrency and database-side questions, then the CTO whiteboard designs, and finally the stories for the HR and project manager round.

Day 1 — Map the loop and set up your language

  • Write the three reported rounds on one page with the question types each covers, and mark the one you are least ready for.
  • Choose one language from the ones reported for the role (Python, Java, JavaScript/TypeScript or Go) and list the standard hash map, deque, heap and lock types you will use in it.
  • Create a blank project with a test runner so every later coding task starts from a running file.

Deliverable: A one-page loop map with your weakest round marked, a language choice with its data structure cheat list, and an empty project that runs tests.

Day 2 — Hash maps and sliding windows

  • Write the two-sum question brute force first, then as a single pass with a hash map, with complexity noted.
  • Write the longest-substring question with a last-seen index and a moving window start, and say why the start never moves backward.
  • Write tests for empty input, no valid pair, duplicate values, all-identical characters and a repeated character that appears before the window start.

Deliverable: Two working functions with complexity notes and one test file containing every edge case listed above.

Day 3 — Linked lists, caches and top-K

  • Write an iterative list reversal, then the recursive one, and test empty, single-node and longer lists.
  • Implement an LRU cache with a hash map and a doubly linked list so get and put are constant time, and explain eviction order.
  • Write a top-K most frequent actions function using a counter plus a heap, and compare it with a full sort.

Deliverable: Working code and tests for the reversal, the LRU cache and the top-K function, each with its complexity written next to it.

Day 4 — Concurrency fundamentals

  • Write a small two-lock program that deadlocks (opposite lock order), reproduce the hang, and capture a thread dump of it using your language's tool.
  • Fix it with consistent lock ordering, then again with a timeout and backoff, and note what each fix costs.
  • Answer aloud the process-versus-thread question and the deadlock question, covering memory sharing, context switching and the four deadlock conditions.

Deliverable: A deadlock reproduction with its thread dump, two working fixes with a note comparing them, and a recorded spoken answer to each concurrency question.

Day 5 — Database-side design and slow queries

  • Whiteboard the URL shortener: how short keys are generated, the table or key-value layout, the read path, and what happens on key collisions.
  • Answer the failover question: replication mode, what zero data loss requires, how a replica is promoted, and the latency cost.

Deliverable: Two annotated whiteboard photos (URL shortener and failover), plus a short note on paging, index use and latency from the practice questions.

Day 6 — Scale, availability and event pipelines

  • Design the distributed cache: partitioning, eviction, replication, and what clients see when a node dies.
  • Design the highly available chat application: connection handling, message storage and ordering, and delivery after a reconnect.
  • Outline the analytical event ingestion service: buffering with a queue, consumers, storage for queries, and behaviour when a consumer lags.
  • Redo one of the three, changing a requirement halfway (ten times the traffic, a data store down), and write what changed.

Deliverable: Three whiteboard outlines each with a stated consistency choice and failure behaviour, plus one revised design showing the requirement change.

Day 7 — Behavioral stories and a full rehearsal

  • Write STAR outlines for the pivot, difficult teammate, initiative, deadline prioritisation and critical feedback prompts, plus one for disagreeing with a product manager.
  • For each outline, underline the sentence stating your own decision and add one result you can defend.
  • Say each story aloud, then a closing mock: one coding question, one design prompt, one story, without notes.

Deliverable: Six STAR outlines with your decision and result marked, and a recorded rehearsal covering one coding, one design and one behavioral answer.

Stories for the HR and project manager round

Candidates report the second round as a conversation with HR and a project manager about collaboration and communication. For a Software Engineer role that works with product managers, design and QA, and spends time on code review, refactoring and technical debt, the strongest stories come from those settings. Reported themes are conflict, delivery under pressure, mentoring and explaining technical constraints to non-technical colleagues. Keep each story to your own decisions and outcomes.

  1. Tell me about a time you disagreed with a product manager on a feature requirement. How did you resolve it?

How do you handle deadlocks in a multithreaded application?

Medium · Technical and Coding Fundamentals

Approach

  1. Start with what a deadlock is: threads that each hold a resource and wait for one another, so none can proceed. It needs four conditions together: mutual exclusion, hold-and-wait, no preemption and circular wait. Removing any one prevents it, and most practical fixes break circular wait or hold-and-wait.
  2. Prevent it in code with a global lock order. For a transfer between two accounts, always lock the lower account id first, whichever direction the money flows. Keep critical sections small and never call callbacks, I/O or unknown code while holding a lock.
  3. Use bounded waiting where ordering is impractical: tryLock with a timeout in Java, acquire(timeout=...) in Python, a context deadline in Go. On failure release everything, wait a randomised backoff and retry, so the two threads do not collide in lockstep again.
  4. Diagnose a live hang by taking a thread dump. A deadlocked process typically sits idle with low CPU. jstack or jcmd Thread.print in Java reports the cycle it finds, py-spy dump or faulthandler works for Python, and a SIGQUIT goroutine dump works for Go. Read which lock each blocked thread holds and which it wants.
  5. Reduce shared state where you can: a single owner thread fed by a queue, immutable data, or concurrent collections. For database deadlocks, access rows in a consistent order and retry the transaction when the database picks a deadlock victim.

Follow-up

  • How does a deadlock differ from a livelock or starvation, and which can your timeout-and-retry approach cause?
  • Does Python's global interpreter lock prevent deadlocks, and why or why not?
  • Two threads each call a method that locks object A and then object B, in opposite orders. How would you find and fix that in an existing codebase?

Tell me about a time you had to pivot your technical approach halfway through a project.

Medium · Behavioral and Cultural Fit

Approach

  1. Pick a pivot that was yours to call and that cost something, such as changing a data store, dropping a library, or replacing an approach once a measurement or requirement change showed it would not work. A pivot you were simply told to make shows less.
  2. Use STAR but spend most of the time on Action. Name the trigger evidence (a load test, a blocked dependency, a requirement from the product manager), the options you compared, and why you chose the new route over finishing the old one.
  3. Show how you handled the sunk cost: what you salvaged from the first approach, how you told the project manager and teammates about the schedule effect, and what you did to limit risk during the switch (feature flag, migration in stages, rollback plan).
  4. Close with the result you actually observed, and one thing you would do earlier next time, such as a spike or a load test before committing. Avoid a story where the first plan was simply stupid; that hides the judgment you want to show.

Follow-up

  • How did you decide it was time to switch rather than push through?
  • How did you explain the schedule impact to the project manager?
  • What would you check before starting next time, so the problem surfaces earlier?

Describe a situation where you had to work with a difficult team member. How did you handle it?

Medium · Behavioral and Cultural Fit

Approach

  1. Choose a real working disagreement with a specific cause, such as repeated review conflicts, missed handoffs or a teammate who dismissed input. Describe behaviour and its effect on the work, not the person's character.
  2. Show a sequence: you raised it directly and privately first, asked about their constraints or view, then agreed on something concrete such as review conventions, a written decision record, or a shared definition of done. Escalate to a manager only if that failed, and say why.
  3. Own your part. A credible story admits something you changed, for example that you had been giving feedback in public threads or had not asked for their reasoning.
  4. State the outcome with evidence, such as reviews that stopped bouncing or a feature that shipped, and the state of the working relationship afterward. A story where you won and they lost, or one with no resolution, is the common failure.

Follow-up

  • What would you have done if the direct conversation had not worked?
  • At what point does a conflict like this go to a manager, in your view?
  • What did you learn about your own communication from that situation?

Give an example of a time you took the initiative to improve a process or tool outside of your normal responsi

Medium · Behavioral and Cultural Fit

Give an example of a time you took the initiative to improve a process or tool outside of your normal responsibilities.

Approach

  1. Pick an improvement outside your assigned work that you spotted from evidence: a flaky test suite, a manual release checklist, a slow local setup, an alerting gap. Say how you noticed it and what it was costing the team.
  2. Show how you proceeded without stepping on others: a small proof of concept, a conversation with the owners of the tool or process, and agreement before you changed shared things. A solo change that bypassed the owners is a risk, not initiative.
  3. Describe how it was adopted: documentation, a walkthrough, review by teammates, and who maintains it now. A tool that only you used ends the story badly.
  4. Report a result you measured, such as build times, number of manual steps removed or incidents avoided. Use only numbers you can defend, and state plainly where the effect was qualitative.

Follow-up

  • How did you get buy-in from the people who owned that process?
  • Who maintains it now, and what happens when you are not around?
  • How did you balance this against your assigned work?

How do you prioritize your tasks when you have multiple urgent deadlines?

Medium · Behavioral and Cultural Fit

Approach

  1. Give the method before the example: list everything with a date, find out from each requester what actually happens if it slips, and rank by impact and cost of delay rather than by who is loudest. Production incidents and security issues come before feature work.
  2. Check dependencies and blockers. Prefer work that unblocks other people first, and look for pieces that can be done in parallel or handed off.
  3. Negotiate openly. If everything cannot fit, go back to the product manager or stakeholders with the options (cut scope, move a date, add a person) and let them choose, with the trade-offs written down. Saying nothing and working longer hours is the answer that fails.
  4. Anchor it in one real situation: what the competing items were, what you put first and why, what slipped and how you communicated it, and what happened. Name the thing you chose not to do.

Follow-up

  • Two stakeholders each insist their item is the top priority. How do you resolve it?
  • How do you handle a production incident that lands in the middle of a sprint commitment?
  • How do you keep a record so others can see why you ordered the work that way?

Tell me about a piece of critical feedback you received and how you acted on it.

Medium · Behavioral and Cultural Fit

Approach

  1. Choose feedback that was genuinely hard to hear and specific, such as code reviews showing under-tested changes, unclear design write-ups, or communication that left a product manager surprised. Avoid disguised strengths like "I work too hard".
  2. Show your first reaction honestly, then what you did to understand it: asked for examples, checked the pattern in your recent work, and confirmed what good would look like with the person who gave it.
  3. Describe concrete changes you made and how they appear in your work, for example a test checklist before each pull request, a short design note before large changes, or earlier status updates to stakeholders.
  4. Give evidence it stuck: later review comments, a follow-up from the same person, or a project where the new habit helped. The weak version is a story that ends at "I took it on board" with no visible change.

Follow-up

  • Did you agree with all of the feedback, and what did you do about the parts you did not?
  • How did you check that the change had really taken hold?
  • Tell me about feedback you gave someone else and how they responded.