Interview guides · A healthcare data
A healthcare data Software Engineer Interview Guide
A healthcare data Software Engineer interview guide: Java, SQL, GC internals, .NET Core, system design and Gitflow questions, plus a 7-day prep plan.
The 60-second brief
Candidates describe A healthcare data as an employer whose Software Engineers build backends and data pipelines that handle sensitive healthcare information. The work they list includes tuning database queries for high-volume transactions, building microservices that let separate systems exchange data, and integrating data models. Candidates also mention connecting to external client systems and following security protocols when handling data. The stack named in reports is Java with Spring Boot and React, with SQL and NoSQL databases, Docker, CI/CD pipelines and cloud services (AWS, Azure or GCP) listed as related skills.
This guide is for Software Engineer candidates preparing for A healthcare data. It covers the five questions candidates report (schema design for a high-traffic application, monolith versus microservices, event loops and asynchronous programming, Gitflow collaboration, and resolving a production bug), plus the topic areas reported alongside them: Java, SQL, garbage collection internals, system design, .NET Core, Docker multi-stage builds, testing and CI/CD. No round order or duration is reported, so the plan builds depth in each topic instead of rehearsing a fixed sequence.
Candidates report technical screens, deep-dive conversations with the future team and a closing conversation about collaboration and leadership. The number of rounds varies with location and hiring urgency, teams sometimes adjust the order, and whether any coding happens live or as a take-home depends on the team.
Focus areas
- Java
- SQL
- Garbage Collection (GC internals)
Overview
The reported topics fall into three groups. The first is language and runtime knowledge: Java, .NET Core, garbage collection internals, and asynchronous programming in the language you choose. The second is data and architecture: designing a schema for a high-traffic application, tuning a slow SQL query in production, and comparing monolithic and microservices architectures. The third is engineering practice: Gitflow, resolving a complex production bug, testing at unit, integration and end-to-end level, Docker multi-stage builds and CI/CD pipelines.
Candidates report that questions are framed as scenarios rather than definitions, so a memorised list of collectors or normal forms will not carry an answer by itself. For each topic, prepare one concrete case you can walk through: a query you made faster and how you proved it, a service boundary you drew or argued against, a pause or memory problem you traced to its cause. Practise saying the trade-off aloud, since candidates also report talking through your reasoning as part of the technical stages.
The data is healthcare information, and candidates list prior exposure to healthcare data standards or regulated industries as a nice-to-have rather than a requirement. If you bring up standards, tie each one to a design consequence. HL7 v2 messages and FHIR resources shape the schema: for example a Patient table with linked Observation rows keyed by patient id and effective time, with the raw inbound message kept for replay. HIPAA's minimum-necessary principle shapes access: each service or role reads only the columns it needs, identifiers live in a separate access-controlled table, and application logs carry record ids rather than names or diagnoses. HIPAA audit expectations shape writes: an append-only audit table recording who read or changed which record and when. Keep these short and attached to a design you describe, not recited as a compliance list.
Because the reported stack mentions Java, Spring Boot and React but the requirements also accept JavaScript and Python, and .NET Core appears among the reported topics, check the job description for the team's actual language and prepare the async and runtime questions in it. For a .NET Core team, the plan covers the CLR side: Task and async/await with SynchronizationContext and ConfigureAwait(false) for async, and CLR generations, the Large Object Heap and workstation versus server GC for memory.
Where to focus your preparation
Candidates describe the process as a mix of technical validation and conversational interviews rather than a fixed list of named rounds, so this guide treats preparation as one block you tailor once your recruiter confirms the format.
Preparation focus
No named round sequence is reported for this loop. Candidates describe technical screens, deep-dive discussions with the future team and a final conversation about collaboration and leadership, and say the order and focus can shift between teams. Coding may be live or a take-home depending on the team. Treat this block as your preparation checklist: ask your recruiter which stages you will face and whether you will write code, then cover the reported topics below. For technical stages, expect scenarios where you apply a concept, such as speeding up a slow query or explaining how a runtime reclaims memory, instead of reciting definitions. For conversational stages, bring stories about trade-offs, code review, disagreement and ownership.
What to demonstrate
- Working knowledge of Java, SQL, garbage collection internals and .NET Core, the topics candidates report
- Ability to apply database, architecture and testing concepts to a specific scenario and state the trade-offs
- How clearly you explain design and debugging decisions, and how you describe working with a team
How to prepare
- Rehearse a spoken walkthrough of tuning a slow production SQL query: read the plan, check indexes and row estimates, change one thing, re-measure
- Write out how you would explain a monolith-to-microservices trade-off for a data-heavy backend, with one concrete boundary and one cost
- Practise a Dockerfile with a build stage and a slim runtime stage, and be able to say what each stage copies forward and why
- Prepare two stories, one about a production bug you traced and one about a team disagreement you settled, each with a measurable result
Where candidates lose points
Answering the schema design question with table names and no traffic reasoning
Start from the access patterns: which queries are hot, the read-to-write ratio, and which columns they filter on. Then choose keys and indexes, say when you would denormalise or partition, and name what each choice makes slower.
Answering the slow SQL query scenario by saying 'add an index' before looking at anything
Describe a sequence: capture the actual query and its parameters, read the execution plan, compare estimated and actual rows, check for scans, sorts and missing or unusable indexes, change one thing, and measure again in a safe environment before touching production.
Naming garbage collectors without saying what they trade off
Explain generations and why most objects die young, what a stop-the-world pause is, and how throughput-oriented and low-pause collectors differ. Then tie it to a symptom you would see, such as long pauses, rising old-generation use or allocation pressure, and the evidence you would read, such as GC logs and heap dumps on the JVM or dotnet-counters and dotnet-gcdump on .NET.
Treating monolith versus microservices as a verdict instead of a trade-off
Compare maintenance and scalability on specific axes: deployment independence, data ownership, network failure modes, testing cost and team size. Give a case where you would keep a monolith and one where you would split a service, and name the cost of the split.
Describing a production bug as a heroic fix with no method
Structure it as: assess impact, mitigate first (roll back, disable a flag), form hypotheses from logs, metrics and recent changes, confirm the cause, fix, add a test, and write up what would prevent a repeat. Include what you did not know at each step.
Questions & practice
Explain the concept of event loops and asynchronous programming in the context of your preferred language.
Medium · Technical and Domain Knowledge
Approach
- Define the model. An event loop is one thread that repeatedly takes a ready task or callback from a queue and runs it to completion. I/O is handed to the OS (epoll, kqueue, IOCP) or a worker pool (libuv uses one for file system and DNS calls), and each completion queues the next callback. That is concurrency without parallelism for your own code.
- JavaScript detail: after each macrotask (timer, I/O callback), the microtask queue (Promise reactions, queueMicrotask) is drained completely. So a resolved promise's .then runs before setTimeout(fn, 0). In Node, process.nextTick callbacks run before promise microtasks, and setImmediate runs in the check phase after poll. async/await is syntax over promises, and each await yields to the loop.
- Python asyncio: async def defines a coroutine, await suspends it, and asyncio.gather or a TaskGroup runs several at once. A blocking call such as time.sleep or a synchronous HTTP or DB client stalls the whole loop, so wrap it with asyncio.to_thread or run_in_executor. CPU-bound work belongs in processes.
- Java: the language has no built-in single event loop. CompletableFuture runs on executors, and Netty and Spring WebFlux (Reactor) use event-loop threads where one blocking call stalls every channel on that thread. Java 21 virtual threads let blocking-style code scale by unmounting at blocking I/O. Before JDK 24, synchronized blocks could pin the carrier thread.
- Close with the trade-off. Async wins for I/O-bound work with many mostly idle connections because it needs little memory per connection. It does nothing for CPU-bound work. The typical bugs are a forgotten await, unhandled promise rejections, and long synchronous loops that starve every other request.
Follow-up
- Predict the output order of console.log, setTimeout(fn, 0), Promise.resolve().then(fn) and process.nextTick(fn) in one Node script, and explain why.
- A request handler runs a two-second CPU calculation on the event loop: what happens to other requests, and how do you fix it?
- How would you call a blocking database driver from an asyncio service without stalling the loop?
Diff a projection against the primary without per-row point reads
Hard · reconciliation · range hashing · throttling
The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.
Approach
- Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
- Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
- For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
- Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.
Follow-up
- The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
- Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
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
- 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.
- 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.
- 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.
- 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?
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
- 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.
- 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.
- 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.
- 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?
Can you describe how you approach designing a scalable database schema for a high-traffic application?
Medium · Technical and Domain Knowledge
Approach
- Start from access patterns, not tables: list the top queries with their rates and the read/write ratio. Keep the transactional core normalized (3NF, foreign keys, NOT NULL and UNIQUE constraints), then denormalize only for a named, measured read path, using a summary table or materialized view.
- Choose keys and indexes on purpose. Use bigint identity or time-ordered UUIDv7 primary keys, since random UUIDv4 values scatter B-tree inserts. Order composite indexes with equality columns first and then the range or sort column, e.g. (tenant_id, status, updated_at). Every extra index adds write cost.
- Scale reads before writes. Read replicas absorb reads, but replica lag means a read-your-own-write request has to go to the primary. Range-partition large time-series tables (e.g. by month) so queries prune partitions and retention becomes a cheap DROP of an old partition.
- Shard only when one primary cannot absorb the writes. Pick a key such as tenant or patient id that keeps most queries on one shard, and name the cost: cross-shard joins, global uniqueness and multi-shard transactions get expensive.
- Plan for schema change and sensitive data. Use expand/contract migrations: add a nullable column, backfill in batches, switch reads, then drop the old column. In PostgreSQL, use CREATE INDEX CONCURRENTLY so writes are not blocked. Keep personal or health fields in a separate, access-controlled, encrypted table.
Follow-up
- How would you paginate a feed of millions of rows, and why is keyset pagination faster than OFFSET on deep pages?
- Which shard key would you choose here, and which query becomes a scatter-gather because of it?
- How do you add a NOT NULL column to a 500-million-row table without downtime?
What are the key differences between monolithic and microservices architectures in terms of maintenance and sc
Medium · Technical and Domain Knowledge
What are the key differences between monolithic and microservices architectures in terms of maintenance and scalability?
Approach
- Define both precisely. A monolith is one deployable unit with in-process calls and usually one database. Microservices are independently deployable services, each owning its data and talking over the network (HTTP, gRPC or a message broker).
- Maintenance trade-off: a monolith gives simple local setup, compiler-checked refactors across modules, one pipeline and ACID transactions, but coupling and build/test time grow with the codebase. Microservices give independent releases and clear ownership, but cost versioned API contracts, per-service CI/CD, containers and orchestration (Docker, Kubernetes), and distributed tracing.
- Scaling trade-off: a stateless monolith scales horizontally behind a load balancer, but every replica carries every component. Microservices scale only the hot service, but each network hop adds latency and new failure modes, so you need timeouts, retries with exponential backoff and jitter, circuit breakers, and idempotency keys on writes.
- Name the data cost. A single database transaction across two services is gone, so you use a saga with compensating actions, and the transactional outbox pattern to publish events reliably. Cross-service joins become API calls or a read-model projection.
- Give a decision rule: start with a modular monolith with enforced module boundaries. Extract a service when a component has a different scaling profile, release cadence or owning team, and do it gradually with the strangler-fig pattern behind a routing layer.
Follow-up
- How would you carve the first service out of an existing monolith without a big-bang rewrite?
- A request spans three services and the second write fails: how do you keep the data consistent?
- How would you find which service is responsible for a latency spike across a five-hop call chain?
Can you walk me through your process for identifying and resolving a complex production bug?
Medium · Technical and Domain Knowledge
Approach
- Triage before root cause. Size the impact (which users, since when, error rate or latency), then mitigate first: roll back the last deploy, turn off a feature flag or scale out. Post status updates while you work.
- Correlate the start time with changes: deploys, config or schema migrations, traffic shifts, dependency incidents. Then follow one failing request through logs by correlation id, rate/error/duration metrics and distributed traces. For a slow database path, read the slow-query log and run EXPLAIN ANALYZE on the offending query.
- Reproduce and narrow it down. Shrink the failing input to a minimal case. Use git bisect between a known-good and a known-bad commit. For intermittent failures, check concurrency and race windows, timeouts and retries, data-dependent branches, time zones, and exhausted resources such as connection pools, memory or long GC pauses (read GC logs, take a heap dump with jcmd on the JVM).
- Fix with proof. Write a failing test that captures the bug, make it pass, ship behind a canary or flag, and watch the same metric that first showed the problem until it returns to baseline.
- Prevent a repeat. Write a blameless postmortem with a timeline, the root cause and the contributing factors, and action items that each have an owner: a missing alert, a regression test, or a guardrail such as a query timeout or input validation.
Follow-up
- The bug appears only under production load and never locally: what do you do next?
- A JVM service's heap grows for days until it throws OutOfMemoryError: how do you find what is leaking?
- How do you decide between rolling back and shipping a hotfix forward?
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
- 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.
- 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.
- 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.
- 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 targets one reported area (Java and .NET runtime and async, garbage collection, SQL, schema and service design, production debugging and delivery tooling, behavioral stories) and ends with something you can show.
Day 1 — Async programming and language fundamentals
- Pick the language you will answer in and write a short explanation of its concurrency model: for Java, threads, executors, CompletableFuture and event-loop servers such as Netty; for JavaScript, the call stack, task queue and microtask queue; for Python, asyncio.
- For a .NET Core team, explain Task and async/await, what SynchronizationContext captures, when ConfigureAwait(false) matters in library code, and why blocking on .Result or .Wait() can deadlock.
- Review Java and Spring Boot basics: the equals/hashCode contract, HashMap versus ConcurrentHashMap, immutability, checked versus unchecked exceptions, dependency injection, why @Transactional is skipped on self-invocation, and how to size a connection pool.
- Write a small program that issues several slow calls concurrently, block it on purpose and note the symptom. Then solve the lease-record practice problem on overlapping attempts and peak concurrency, and explain the sweep (sort start and end events, keep a running count) and its O(n log n) cost.
Deliverable: A one-page note on your language's async model (with the .NET variant if relevant), a Java and Spring Boot fundamentals cheat sheet, a concurrent program with its deliberate blocking bug, and one solved practice problem.
Day 2 — Garbage collection internals (JVM and .NET)
- Write from memory how the JVM heap is organised into young and old regions, how objects are promoted, and what stop-the-world means. Compare a throughput collector with a low-pause one.
- Run a small Java program that allocates heavily with GC logging enabled and read the log: note collection frequency, pause lengths and heap after each collection.
- For .NET Core, cover CLR generations 0, 1 and 2, the Large Object Heap, workstation versus server GC, IDisposable versus finalizers, and how to read dotnet-counters and dotnet-gcdump output for the same allocation-heavy program.
- List four causes of memory pressure you could explain, such as unbounded caches, large collections held in static fields, listener leaks and excessive short-lived allocation, and the evidence that points to each.
Deliverable: A GC cheat sheet in your own words covering the JVM and the CLR, and an annotated GC log or counters output from your own run with a one-paragraph interpretation.
Day 3 — SQL tuning and query plans
- Take two slow queries on a test database, read each execution plan, and write what is wrong with it (scan, sort, mismatched types, unusable index) and the fix.
- Work the practice problems on an owner filter that ignores its listing index and on replacing offset paging with keyset paging, and explain when each change helps.
- Practise saying the full spoken answer to optimising a slow query in production: gather evidence, change one variable, verify, and roll back safely.
Deliverable: Two before-and-after query plans with timings, plus a spoken answer outline for the production tuning scenario.
Day 4 — Schema design and architecture trade-offs
- Design a schema for a high-traffic application of your choice. Write the hot queries first, then the tables, keys and indexes, and note what you would partition, cache or denormalise and what that costs.
- Write a comparison of monolith and microservices for the same system: where you would draw the first service boundary, how data would be owned, and what would fail differently across a network.
- Add a sensitive-data pass: mark which columns hold patient or personal identifiers, move them to an access-controlled table, keep them out of logs, and add an append-only audit table recording who read or changed which record.
Deliverable: A schema with indexes and a hot-query list, plus a one-page monolith-versus-services recommendation with three costs of splitting.
Day 5 — Production debugging, testing and delivery tooling
- Practise the production bug walkthrough aloud: impact, mitigation, hypotheses, evidence, fix, regression test, follow-up. Use a real incident from your work if you have one.
- Work the practice problem about listing latency that scales with page size, and narrate each diagnostic step you take.
- Write a multi-stage Dockerfile for a Java service (build stage with the build tool, runtime stage with only the packaged artifact) and sketch a CI pipeline that runs unit, integration and end-to-end tests before an image is built.
Deliverable: A written debugging narrative, a working multi-stage Dockerfile and a pipeline outline listing which test layer runs at which step.
Day 6 — Behavioral stories and Gitflow
- Write a Gitflow explanation that covers feature, release and hotfix branches, merge direction for hotfixes, and one honest drawback; then attach it to a real team situation.
- Prepare a story where you made a performance versus delivery-speed trade-off, one about a disagreement settled with evidence, and one about helping a peer.
- Rehearse the practice scenarios about shipping under a deadline and about committing to a design you argued against.
Deliverable: Three written stories in situation, decision, result form and a spoken Gitflow answer under two minutes.
Day 7 — Full mock and questions to ask
- Run a mock with a friend or recorded session: one design question, one SQL scenario, one runtime question (async or GC) and one behavioral story, answered aloud.
- Review the recording for places where you stated a technique without its trade-off and add the missing trade-off to your notes.
- Write the questions you will ask: which languages and frameworks the team uses, how releases are branched and shipped, and how the team handles on-call and incidents.
Deliverable: A recorded mock with a list of gaps fixed and a short list of questions for the interviewers.
Collaboration, Gitflow and trade-off stories
Candidates report conversational stages about past experience, collaboration and growth, and one reported question asks how you use Gitflow in a team. For a Software Engineer, prepare stories that show you made a decision, changed code in a shared repository, handled review and disagreement, and can state a trade-off between performance and development speed.
- Tell me about a time you chose between performance and development speed. What did you pick, and what did it cost?
- Describe a disagreement with a teammate about a technical decision and how you settled it.
- Tell me about a time you helped a teammate improve through code review or mentoring.
- Describe a project where requirements were unclear. How did you break it into steps and confirm direction with others?
How do you handle version control and collaborate effectively using Gitflow in a team environment?
Medium · Technical and Domain Knowledge
Approach
- Choose a real case where Git workflow was part of the outcome: a release that needed a hotfix while the next feature set was still in review, or a long-lived branch that diverged and hurt a team. Say your own role in the decision, not just what the team did.
- Describe the branching model concretely: long-lived main and develop branches, short-lived feature branches off develop, release branches for stabilisation, hotfix branches off main that are merged back into both main and develop, and tags on main for each release. Naming the hotfix double-merge shows you have run it.
- Cover collaboration mechanics: pull requests with required review, a CI check before merge, small commits with clear messages, rebasing a feature branch on develop before opening the request, and how you settled a conflict by talking to the other author instead of picking a side silently.
- Give a trade-off. Gitflow suits scheduled releases, but long-lived branches delay integration and make conflicts larger. Say when you would shorten branch life, use feature flags or lean toward trunk-based development, and what you did in your story to keep merges small.
- Close with the result and one lesson: fewer failed releases, a hotfix shipped without blocking feature work, or a rule your team adopted. The mistake that sinks this answer is listing git commands with no team situation behind them.
Follow-up
- A hotfix landed on main while a release branch was open. How do you make sure the fix reaches both the release branch and develop?
- How would you undo a merge that has already reached develop and been pulled by teammates?
- When would you move a team from Gitflow to trunk-based development, and what would you need in place first?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?