cswork / libraryOpen practice workspace

Interview guides · BonusHub Digital Solutions

BonusHub Digital Solutions Software Engineer Interview Guide

BonusHub Digital Solutions Software Engineer interview guide: three reported rounds, C/C++ and .NET questions, POS system design and a 7-day plan.

The 60-second brief

BonusHub Digital Solutions is described as a company that builds and runs digital platforms for clients. Its Software Engineer postings mention .NET application development and C/C++ systems for point-of-sale (POS) environments. Candidates describe the work as covering the whole development lifecycle, from requirements through deployment and upkeep, plus fixing production problems and shipping features for POS and network-managed systems alongside other teams, and reducing technical debt. The requirements candidates describe list C/C++ or .NET, Windows, VMware and real-time or high-performance systems, with cloud deployment, containers, network administration protocols and POS or fintech experience as extras.

This guide is for candidates interviewing for a Software Engineer position at BonusHub Digital Solutions on either the C/C++ or the .NET side. It covers the three reported rounds, all thirteen reported questions (nine technical and design, four behavioral), the Windows, VMware and POS context around them, and a seven-day plan.

Candidates report three rounds over roughly 3-5 weeks: Technical Screening, Architectural Discussions and Behavioral Assessments. Candidates also report that the specific technical content is tailored to the team you interview with.

Focus areas

  • VMware
  • .NET
  • Windows Administration

Overview

The reported stack is narrower and lower-level than in many software loops. Candidates describe roles in C/C++ or .NET, running on Windows, sitting on VMware infrastructure, and in some cases serving POS environments where real-time or high-performance behaviour matters. That shapes preparation: you need a precise vocabulary for ownership and lifetime of memory, threads versus processes, locking, and how a request moves through a .NET application, rather than a broad survey of web frameworks.

The nine technical and design questions candidates report fall into three clusters. Language and runtime questions cover C++ memory management and leak prevention, threads versus processes on Windows, the lifecycle of a request in a .NET web application, and dependency injection in .NET Core. Data and architecture questions cover tuning database queries for busy POS systems, a distributed POS design that survives intermittent connectivity, a multi-threaded design that makes full use of the CPU, and high availability for a Windows-based service. The ninth question is a debugging one: how you track down a race condition.

Candidates also describe example scenarios such as refactoring a snippet to make it thread safe and explaining what a data structure costs in a specific context. Technical depth areas they list are pointers, smart pointers and garbage collection, locks and mutexes, and profiling. Practise these by writing code yourself, because candidates are told to expect a whiteboard or shared editor with no IDE to catch syntax or fill in library calls.

The behavioral round is reported as a check on collaboration and fit, and the four reported prompts are about optimising slow code, architectural disagreements, a hard problem under deadline, and speed versus maintainability. All four are best answered with real engineering stories that include measurements and trade-offs. Candidates also describe clarifying constraints before answering, naming the bottleneck early, stating trade-offs such as speed against memory, and covering edge cases, so build those habits into every practice answer.

The BonusHub Digital Solutions interview process

Candidates describe three rounds in this order: Technical Screening, Architectural Discussions and Behavioral Assessments. The round names are quoted below; which questions come up in each round varies, so prepare by question type.

Technical Screening

Candidates describe this first stage as an assessment of technical depth through hands-on coding. The technical-depth areas they list are memory management (pointers, smart pointers, garbage collection), concurrency (locks, mutexes, thread safety) and performance tuning with a profiler. Example scenarios they mention are refactoring a snippet for thread safety and explaining the performance impact of a data structure in a given context. Since the specific technical rounds are reported to be tailored to the team, confirm with your recruiter whether the coding is in C/C++ or .NET and prepare in that language. Practise writing code by hand or in a plain editor.

What to demonstrate

  • Hands-on coding in the language you applied with, C/C++ or .NET, written in a shared editor or on a whiteboard
  • Memory ownership and lifetime: pointers, smart pointers, garbage collection
  • Thread safety and locking: mutexes, shared state, spotting an unsafe snippet
  • Profiling and data-structure cost: finding a bottleneck and explaining why it is slow

How to prepare

  • Write from memory a RAII wrapper and a unique_ptr versus shared_ptr example, including a shared_ptr cycle fixed with weak_ptr; on the .NET side, write an IDisposable class with a using block and explain what the garbage collector does and does not release.
  • Take a small class with a shared counter and a shared container, make it thread safe, and say why you chose a mutex scope or an atomic.
  • Practise the questions on C++ memory management and on threads versus processes on Windows aloud, then the race-condition debugging question as a step-by-step sequence.

Architectural Discussions

Candidates describe this stage as in-depth conversations about system design and architecture. The areas they list are scalability (how software copes with more load), reliability (error handling and recovery) and integration (how a module fits existing Windows or POS infrastructure). Reported design questions include a distributed POS system that must cope with intermittent network connectivity, a multi-threaded application that maximises CPU use, and high availability for a Windows-based service. Candidates are advised to state assumptions, name constraints first, and discuss trade-offs in latency, throughput and resource limits.

What to demonstrate

  • System-level thinking: moving from a single component to scalability, reliability and integration
  • Trade-off discussion between architectural patterns in terms of latency, throughput and resource limits
  • Failure handling: error handling, recovery and behaviour when the network or a service drops
  • Fit with existing Windows or POS infrastructure

How to prepare

  • Whiteboard the offline POS design: a local durable queue on each terminal, uploads with idempotency keys, a sync service that deduplicates, and a stated conflict rule for prices and stock.
  • Prepare the multi-threaded design answer: separate CPU-bound from I/O-bound work, size the thread pool by core count, partition data to limit locking, and name how you would measure utilisation.
  • Write a short list for a highly available Windows service: redundant instances, health checks, automatic restart, failover, state kept outside the process, and where VMware features such as live migration or host-level failover help and where they do not.
  • Rehearse the request lifecycle and dependency injection answers for .NET, and the database tuning answer for POS workloads.

Behavioral Assessments

Candidates describe this final stage as an evaluation of cultural fit and collaboration skills. The reported prompts are about optimising code that caused performance bottlenecks, handling disagreements over architectural decisions, solving a complex technical problem under a tight deadline, and balancing rapid feature delivery against clean, maintainable code. Prepare two or three real stories and map each to more than one prompt.

What to demonstrate

  • Collaboration: how you handled disagreement with teammates and how it was resolved
  • Ownership: which decisions were yours, with a measured outcome
  • Judgement under pressure: what you cut or deferred when a deadline was tight
  • Balance between delivery speed and maintainability, including how technical debt was handled

How to prepare

  • Write each story in four short parts: situation, your decision, the evidence or measurement, and the result including the trade-off you accepted.
  • Choose one story with performance numbers before and after, one with a design disagreement, and one with a deadline; check that each uses "I" for your own actions.
  • Rehearse aloud until each story fits comfortably in a couple of minutes.

Where candidates lose points

Answering the C++ memory management question as free whatever you allocate with new

Give the ownership model: stack versus heap, RAII, unique_ptr for single ownership, shared_ptr with weak_ptr to break cycles, and tools for finding leaks. On the .NET side, explain garbage-collected memory versus unmanaged resources and IDisposable.

Tuning POS database queries by adding indexes without evidence

Start from the slow statement and its execution plan, check selectivity and the access pattern, then consider covering indexes, batching, shorter transactions and caching read-mostly data. Say what each index costs on writes.

Designing the distributed POS system as if the network were always up

Start with the disconnected case: each terminal keeps a durable local queue, uploads carry idempotency keys so retries do not double-charge or double-count, and the server deduplicates. State a conflict rule and how a terminal catches up after reconnecting.

Answering the race-condition debugging question with just add a lock

Give a sequence: reproduce under load or with delays, identify the shared state and every writer, use tools such as thread sanitizers or logging with thread ids, fix by narrowing shared state, immutability or a clearly scoped mutex, then add a stress test. Mention deadlock risk.

Telling behavioral stories with no numbers and no personal decision

For each of the four reported prompts, prepare a story with a measurement before and after, a decision that was yours, and the trade-off you accepted. Use "I" for your own actions.

Questions & practice

Explain the memory management model in C++ and how you prevent memory leaks.

Medium · Technical Proficiency and Domain Knowled

Approach

  1. Name the storage durations: automatic objects live on the stack and are destroyed at scope exit in reverse order of construction; static and thread-local objects live for the program or the thread; dynamic objects come from new/delete on the heap. There is no garbage collector, so I tie every heap allocation to an owner.
  2. RAII is the main tool: the destructor releases the resource, so cleanup also runs when an exception unwinds the stack. A raw new followed by code that throws before the delete is the classic leak.
  3. Smart pointers: std::unique_ptr for sole ownership (move-only, no extra cost with the default deleter), std::shared_ptr for shared ownership (atomic reference count in a control block), std::weak_ptr to break cycles, because two shared_ptrs that point at each other are never freed. I create them with make_unique and make_shared.
  4. Rules that prevent leaks and undefined behaviour: pair new[] with delete[], give a polymorphic base class a virtual destructor, and follow the rule of zero (or five) so copies and moves never double-free or drop ownership.
  5. Detection: Valgrind or LeakSanitizer on Linux; on Windows the CRT debug heap (_CrtSetDbgFlag, _CrtDumpMemoryLeaks) or UMDH heap snapshots compared over time for a slowly growing process.

Follow-up

  • When is std::shared_ptr the wrong choice, and what does make_shared change about allocation and about when memory is released while weak_ptrs still exist?
  • A long-running Windows service grows slowly in production. How do you tell a real leak from heap fragmentation or an unbounded cache?
  • Why does deleting a derived object through a base pointer without a virtual destructor cause trouble?

What are the key differences between a thread and a process in a Windows environment?

Medium · Technical Proficiency and Domain Knowled

Approach

  1. A process is a resource container: its own virtual address space, handle table, security token and at least one thread, created with CreateProcess (Win32 has no fork). A thread is what the kernel schedules: its own stack, register context, priority and thread-local storage, sharing the address space and handles of its process.
  2. Cost: creating a process means a new address space plus loading the image and its DLLs, so it is far heavier than CreateThread or _beginthreadex. Switching between threads of different processes also switches address spaces, which costs more than switching within one process.
  3. Isolation: an unhandled exception in any thread ends the whole process by default, so a separate process is the way to contain a fragile component such as a device integration; threads trade that isolation for cheap shared memory.
  4. Communication and sync: threads in one process use CRITICAL_SECTION, SRW locks, events and atomics (Interlocked*); processes use named pipes, memory-mapped files (CreateFileMapping), sockets or RPC, and named kernel objects (mutexes, events, semaphores) that can be shared across processes.
  5. Close with the decision rule: threads for parallel work on shared data inside one component, processes for fault and security isolation, accepting the IPC cost.

Follow-up

  • What is the difference between a CRITICAL_SECTION and a Win32 mutex, and when do you need the mutex?
  • For a point-of-sale application that talks to card readers and printers, would you put the device code in a thread or a separate process? Why?
  • What happens to the other threads when one thread hits an access violation?

How do you approach debugging a race condition in a multi-threaded application?

Medium · System Design and Architecture

Approach

  1. Characterise the symptom: what goes wrong, how often, and whether it disappears under a debugger or extra logging (timing changes hide races). Then make it more frequent with a stress loop, more threads than cores, and injected yields or sleeps at the suspected interleaving points.
  2. Collect evidence with little timing impact: thread id and sequence numbers logged into an in-memory ring buffer instead of synchronous file writes. For hangs, take a process dump (ProcDump) and inspect every thread's stack in WinDbg (~*k) to see who holds which lock.
  3. Use tools: ThreadSanitizer (-fsanitize=thread with clang or gcc) or Helgrind for C++ data races on Linux builds; on Windows, Application Verifier's lock checks for misuse of critical sections.
  4. Find the shared state and the broken invariant, usually a check-then-act or read-modify-write: count++ is not atomic, lazy initialisation without synchronisation, a collection iterated while another thread modifies it.
  5. Fix it at the right granularity: one lock around the whole compound operation, an atomic (std::atomic, Interlocked.Increment) for a single variable, or no sharing at all (immutable data, message passing). Keep a consistent lock order so the fix cannot deadlock, then prove it with a long stress run and a clean ThreadSanitizer report.

Follow-up

  • What is the difference between a data race and a race condition, and can code with only atomics still have a race condition?
  • Why does adding a log statement often make the bug disappear?
  • How would you write a regression test that reliably catches this race in the future?

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.

Approach

  1. Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
  2. Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
  3. For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
  4. State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.

Follow-up

  • A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
  • How do you honour an erasure request while resource_revision.actor_user_id still references this table?

Denormalise tenant onto revisions and backfill it live

Hard · denormalisation · online migration · backfill

resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.

Approach

  1. Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
  2. Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
  3. Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
  4. Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.

Follow-up

  • The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
  • How do you verify the backfill actually finished, given rows are still being inserted while it runs?

Describe the lifecycle of a request in a.NET web application.

Medium · Technical Proficiency and Domain Knowled

Approach

  1. Ask which stack: ASP.NET Core or classic ASP.NET on IIS. For ASP.NET Core, Kestrel accepts the connection, either alone, behind a reverse proxy, or behind IIS through the ASP.NET Core Module (in-process or out-of-process), and the request becomes an HttpContext.
  2. The middleware pipeline runs in the order it is registered in Program.cs: exception handling, HTTPS redirection, static files, routing, CORS, authentication, authorization, then the endpoint. Any middleware can short-circuit, and the response passes back through the same middleware in reverse order.
  3. A DI scope is created per request: scoped services such as a DbContext are resolved once for that request and disposed when it ends.
  4. For an MVC or API controller: route match, then filters in order (authorization, resource, model binding and validation, action filters around the action, result filters, result execution), with exception filters handling errors, and the result serialised to the response body.
  5. Classic ASP.NET: HTTP.sys hands the request to the w3wp worker process, and the HttpApplication pipeline raises BeginRequest, AuthenticateRequest, AuthorizeRequest and so on through the HttpModules until a handler runs, ending with EndRequest. Naming both shows you know which one a codebase uses.

Follow-up

  • Where would you add correlation-id logging middleware, and what breaks if you register authorization before authentication?
  • What goes wrong if a singleton service takes a scoped DbContext in its constructor?
  • How does async/await in a controller action change thread-pool usage under load?

How do you optimize database queries for high-traffic POS systems?

Medium · Technical Proficiency and Domain Knowled

Approach

  1. Measure before changing anything: rank queries by total time and frequency (Query Store or sys.dm_exec_query_stats on SQL Server, pg_stat_statements on Postgres) and read the actual execution plan for scans, key lookups, spills and bad row estimates.
  2. Index for the hot path: a composite index matching the equality-then-range predicates (for example store and business date), with included columns so it covers the query. Keep predicates sargable (no function wrapped around the column). Every extra index slows inserts on write-heavy transaction tables, so keep only the ones the plans use.
  3. Fix query shape: remove ORM N+1 patterns (eager load or project in Entity Framework), select only needed columns, use parameterised queries for plan reuse (watch for parameter sniffing), insert a basket's line items in one batch, and keep transactions short so locks are held briefly.
  4. Separate workloads: keep checkout on the OLTP database and move end-of-day and reporting queries to a replica or reporting store. Cache reference data such as prices and the product catalogue at the app tier or terminal with explicit invalidation, and use keyset pagination instead of large OFFSETs.
  5. Reduce contention: row-versioning isolation (READ COMMITTED SNAPSHOT on SQL Server) stops readers from blocking writers, and identity columns or sequences replace a single hot counter row for receipt numbers.

Follow-up

  • A query is fast in testing but slow at peak. How do you tell a plan regression from lock blocking?
  • How would you run end-of-day sales reports without slowing checkout?
  • When would you denormalise a POS table, and what does that cost you?

What are the advantages of using dependency injection in.NET Core?

Medium · Technical Proficiency and Domain Knowled

Approach

  1. Core benefit: classes depend on abstractions passed through the constructor instead of creating concrete types, so implementations can be swapped (a fake payment gateway in tests, a different provider per environment) without editing the consumers.
  2. Testability: dependencies are explicit constructor parameters, so unit tests pass mocks or fakes directly, with no static state to reset.
  3. Lifetime management: the built-in container handles Transient (new instance per resolution), Scoped (one per request scope) and Singleton (one per app), and disposes the IDisposable instances it created. It also connects configuration through IOptions<T>.
  4. Single composition root: all wiring sits in Program.cs, which makes the dependency graph reviewable. The built-in container supports constructor injection and open generics, keyed services since .NET 8, and no property injection.
  5. Costs to name: a missing registration only fails when that service is resolved (ValidateOnBuild catches it at startup); a singleton capturing a scoped service is a captive dependency (scope validation is on by default in Development); injecting IServiceProvider everywhere turns into a service locator; a constructor with many parameters points to a class that does too much.

Follow-up

  • How do you use a scoped DbContext from a BackgroundService, which is registered as a singleton?
  • You register two implementations of the same interface. What does constructor injection give you, and how do you choose one explicitly?
  • When would a static helper be fine instead of an injected service?

How would you architect a distributed POS system to handle intermittent network connectivity?

Medium · System Design and Architecture

Approach

  1. Start with requirements: which operations must keep working offline (scanning, pricing, cash sales, receipt printing) and which need central authority (card authorisation, gift card or loyalty redemption, cross-store inventory). Set explicit limits for offline mode rather than allowing everything.
  2. Local-first per store: each terminal or an in-store server keeps a local database with a versioned catalogue and price snapshot. Each sale is written to a durable local outbox in the same local transaction, and a sync agent forwards the outbox when the link returns, retrying with exponential backoff and jitter.
  3. Idempotency: every transaction gets a globally unique id at the terminal (a GUID, or store + terminal + sequence), and the server applies it with a unique constraint or upsert. At-least-once delivery plus an idempotent consumer means replays never double-count.
  4. Conflicts: sales are append-only events and rarely conflict. Shared quantities (stock, loyalty points) sync as deltas, not absolute values. Master data flows down from the central authority with version numbers. Ordering uses sequence numbers, not terminal wall clocks, which drift.
  5. Operations: an in-store hub lets terminals coordinate over the LAN while the WAN is down. Monitor queue depth and the age of the oldest unsynced transaction, and alert per store.

Follow-up

  • Two stores sell the last unit of an item while both are offline. What happens, and how do you reconcile it?
  • A terminal's disk fails before its outbox syncs. What is lost, and how would you reduce that exposure?
  • How do you roll out a price change when some stores are offline for hours?

Explain how you would design a multi-threaded application to maximize CPU utilization.

Medium · System Design and Architecture

Approach

  1. Classify the work first. For CPU-bound work, size the worker count to the logical cores (std::thread::hardware_concurrency, Environment.ProcessorCount), because extra threads only add context switches. For I/O-bound work, use asynchronous I/O (I/O completion ports, async/await) instead of blocking more threads.
  2. Use a thread pool with a task queue rather than a thread per job: TPL (Parallel.For, Task.Run) in .NET, or a fixed pool of std::thread workers in C++. Work-stealing queues balance uneven tasks. Chunk size is a trade-off: too small and scheduling overhead dominates, too large and some cores sit idle at the end.
  3. Minimise shared mutable state: partition the data, keep per-thread accumulators and reduce them at the end, and keep locks off the hot loop. Watch for false sharing: pad per-thread counters to separate cache lines (commonly 64 bytes; C++17 has std::hardware_destructive_interference_size).
  4. Bound expectations with Amdahl's law: speedup <= 1 / ((1 - p) + p / n), where p is the parallel fraction, so the serial part caps the gain. For staged work, use a producer-consumer pipeline with bounded queues for backpressure.
  5. Verify with a profiler (Windows Performance Analyzer with ETW, the Visual Studio profiler, perf on Linux): check whether cores are busy doing useful work, or spinning, waiting on locks, or stalled on memory bandwidth.

Follow-up

  • CPU usage is at 100% but throughput did not improve when you added threads. What do you check?
  • How does hyper-threading change the number of worker threads you would pick?
  • When, if ever, would you set processor affinity?

What strategies would you use to ensure high availability for a critical Windows-based service?

Medium · System Design and Architecture

Approach

  1. Define the target and failure modes first: process crash, hang, host or VM failure, dependency outage and deployments. Each needs its own mechanism.
  2. Process level: configure Service Control Manager recovery actions (restart on first, second and later failures, via sc failure or the service properties). The SCM only sees exits, so add a heartbeat or health endpoint and a watchdog to catch hangs. Handle stop requests (OnStop or SERVICE_CONTROL_STOP) to drain work cleanly.
  3. Host level: run at least two instances. A stateless service goes behind a load balancer with health checks; an active/passive stateful service uses Windows Server Failover Clustering (generic service role). On VMware, vSphere HA restarts VMs on another host after a host failure (downtime of a reboot), and Fault Tolerance keeps a lockstep secondary VM for continuous availability.
  4. State and correctness: move state into a replicated store (for example SQL Server Always On availability groups), make work idempotent so a failover can replay it, and use a lease or leader election when only one instance may process a job.
  5. Removing single points of failure and change risk: redundant database, network and DNS paths; rolling or blue-green deployments; Event Log plus external monitoring with alerts on health checks, not only on process state.

Follow-up

  • How do you prevent split brain when both nodes believe they are active?
  • The service hangs instead of crashing. How do you detect that and recover?
  • How would you apply Windows patches to the hosts without downtime?

One customer endpoint stalls deliveries to every other destination

Medium · bulkhead · timeouts · saturation

The egress service delivers about 1.5k webhooks/second across 40,000 destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. Throughput falls to 300/second, queue depth climbs, and p99 delivery latency for unaffected destinations goes from 200 ms to minutes, while the error rate barely moves. One tenant holds 900 destination rows whose URLs share a hostname that now answers in 9.5 seconds. Explain the mechanism with the arithmetic, then give the containment in the order you would apply it.

Approach

  1. Look at saturation before errors. A flat error rate with collapsing throughput says nothing is failing, things are waiting, so the first signal to pull is in-flight request count or pool wait time rather than the error counter. This is the distinction that decides the whole investigation.
  2. Group in-flight work by resolved host, not by destination id. The cap is keyed per destination row, so 900 rows sharing one hostname buy 3,600 concurrent slots against a single host, each held for 9.5 seconds. The bulkhead was never a bulkhead for that host, and grouping by the wrong dimension is why the dashboard looked healthy.
  3. Do the arithmetic in both directions. Required concurrency is arrival rate times latency, so 1.5k/second at 200 ms needs about 300 in flight, which is entirely consumed by 3,600 slow slots; conversely whatever concurrency is left sustains rate equals concurrency divided by 9.5 seconds, which is the 300/second you are seeing. Matching both numbers is what promotes this from a plausible story to the mechanism.
  4. Explain why the circuit breaker never helped. It opens on consecutive failures, and a 9.5-second response inside a 10-second timeout is a success. Slow is not failing, so an error-rate breaker cannot see this; you need a slow-call ratio, a deadline propagated from the caller's remaining budget, or a concurrency limiter.

Follow-up

  • The host recovers to 80 ms. How long does the queue take to drain, and what does the drain do to the recovered host?
  • Where should the 10-second timeout number actually come from?

Suggested practice checklist

The plan follows the reported loop: language and runtime depth first, then data and architecture, then behavioral stories.

Day 1 — Map the loop and pick your track

  • Write the three reported rounds (Technical Screening, Architectural Discussions, Behavioral Assessments) and, under each, the question types you expect.
  • Decide whether you will be tested in C/C++ or .NET and ask the recruiter to confirm; list Windows, VMware and POS basics as secondary topics.
  • Rate yourself on each of the thirteen reported questions and mark the three weakest.

Deliverable: A one-page loop map with your language track chosen and the three weakest questions marked.

Day 2 — Memory, threads and processes

  • Without an IDE, write a RAII wrapper, a unique_ptr and shared_ptr example, and a weak_ptr fix for a cycle; on the .NET track write an IDisposable example and note what the garbage collector does not release.
  • Write a comparison of threads and processes on Windows covering address space, handles, stacks, context-switch cost and crash isolation.
  • Answer both questions aloud.

Deliverable: A hand-written ownership example, a thread-versus-process comparison sheet, and two recorded spoken answers.

Day 3 — Concurrency and race conditions

  • Make a small shared-state class thread safe, then explain the locking scope and the alternative of an atomic or immutable design.
  • Outline how you would size a thread pool for CPU-bound and I/O-bound work, and how you would measure utilisation.

Deliverable: A thread-safe snippet, a one-page race-condition debugging sequence, and the practice debugging exercise worked.

Day 4 — .NET request lifecycle and dependency injection

  • Draw how a request moves through an ASP.NET Core application from the server to the response, with middleware, routing and the action, and note how classic ASP.NET differs.
  • Write a service registered with transient, scoped and singleton lifetimes and show a captive-dependency bug, then fix it.
  • Write a unit test that replaces a dependency with a fake, to show the advantage of dependency injection.

Deliverable: A request-lifecycle diagram, a lifetime example with the captive-dependency fix, and a short test using a fake.

Day 5 — Query tuning for busy POS workloads

  • Take a receipt-lookup query, read its execution plan and write the index you would add and the write cost it carries.
  • List other levers: batching inserts, shorter transactions, avoiding row-by-row calls, and caching read-mostly reference data.

Deliverable: A tuning checklist with one worked before-and-after plan, plus the two practice SQL exercises completed.

Day 6 — Architecture: offline POS and high availability

  • Whiteboard the offline POS design: local queue, idempotent uploads, server-side deduplication, conflict rule and catch-up after reconnecting.
  • Whiteboard high availability for a Windows-based service: redundant instances, health checks, restart and failover, state outside the process, and what VMware host failover or live migration does and does not cover.
  • For each design, write the main trade-off and what fails first at ten times the load.

Deliverable: Two whiteboard diagrams, each with a trade-off list and a failure analysis.

Day 7 — Behavioral stories and a full mock

  • Write stories for the four reported prompts (optimising slow code, architectural disagreement, a deadline, delivery speed versus clean code) with measurements and decisions.
  • Rehearse each aloud using "I".
  • Run a mixed mock: one coding question in a plain editor, one design question on a whiteboard, one behavioral story.

Deliverable: A story sheet covering the four prompts with numbers, plus notes from the mixed mock.

Stories for the behavioral round

Candidates describe the final round as an assessment of cultural fit and collaboration. The reported prompts are about performance work, architectural disagreement, a deadline and delivery speed versus maintainability, so prepare stories from real engineering work, with a measurement or outcome in each and a clear statement of the decision that was yours.

  1. Describe a production issue you helped resolve with people from another team.
  2. Describe a time you found and paid down technical debt.

Describe a time you had to optimize a piece of code that was causing performance bottlenecks.

Medium · Problem-Solving and Behavioral

Approach

  1. Pick a bottleneck you personally measured and fixed. Open with the symptom in one or two sentences (a slow endpoint, a batch run that overran, a UI stall) and the number that showed it: latency percentile, throughput, CPU, memory or query time.
  2. Show how you found the cause instead of guessing: a profiler or sampling run, a query plan, an allocation or lock-contention trace. Name the saturated resource (CPU, memory and allocations, disk or network I/O, lock waits), because the fix differs for each.
  3. Describe the change concretely, for example replacing a linear scan with a hash lookup, removing per-item allocations or copies, batching round trips, adding an index, or narrowing a lock, and say why you chose it over the alternatives.
  4. Finish with the measurement on the same benchmark after the change, the trade-off you accepted (memory, complexity, readability), and what you added to prevent a regression, such as a benchmark or test. Spend most of the answer on how you found and chose the fix.

Follow-up

  • How did you know that code was the bottleneck and not something around it?
  • What did the optimisation cost in readability or memory?
  • How did you stop the improvement from regressing later?

How do you handle disagreements regarding architectural decisions within your team?

Medium · Problem-Solving and Behavioral

Approach

  1. Choose a real disagreement over a technical option, for example synchronous versus queued processing, a shared library versus a separate service, or two database designs. State both positions fairly in a sentence each; a story where the other side was plainly wrong shows little.
  2. Turn the argument into criteria: latency and throughput targets, failure behaviour, operational effort, team familiarity, and how costly the decision is to reverse. Say what evidence you gathered, such as a spike, a benchmark, a load test or production data, instead of arguing from preference.
  3. Explain how it was settled and recorded: a short written decision listing the alternatives and reasons, agreed by the people who own the code. If you were overruled, show that you committed to the outcome and raised the agreed review point later.
  4. Close with the result and what you changed in how you work, keeping the focus on your own actions: what you suggested, what you conceded, what you asked of others. Never describe a colleague as the problem.

Follow-up

  • What would you have done if no benchmark could settle the question?
  • Tell me about a time you were overruled and what you did next.
  • How do you stop an agreed decision from being reopened every sprint?

Tell us about a complex technical problem you solved under a tight deadline.

Medium · Problem-Solving and Behavioral

Approach

  1. Pick a problem with real technical difficulty, such as a production defect, a data migration, a concurrency bug or an integration with a fixed date, and state the deadline and the stakes in one sentence so the pressure is concrete.
  2. Show triage: you separated what had to ship from what could wait, cut scope with the owner's agreement, and tested the riskiest part first. A feature flag, staged rollout or ready rollback plan shows you managed the risk that speed created.
  3. Walk through the technical path: how you narrowed the problem (reproduce, bisect, add logging), the options you weighed, and why you took the shortcut you did. Name the shortcut honestly instead of presenting it as ideal.
  4. Say how you kept stakeholders informed during the work and what you did afterwards: a follow-up ticket, added tests, repaid debt. A story that is only long hours and heroics leaves out the engineering.

Follow-up

  • What did you deliberately leave out, and who agreed to that?
  • What went wrong after release, if anything?
  • Would you make the same trade-offs again?

How do you balance the need for rapid feature delivery with the need for clean, maintainable code?

Medium · Problem-Solving and Behavioral

Approach

  1. Open with a position that has two sides: speed and maintainability are both needed, and the skill is knowing which corners are safe to cut. Then support it with one real situation rather than a slogan.
  2. Separate what you will not trade away (tests around core logic, clear module boundaries, interfaces so parts can be replaced, no duplicated logic on risky paths) from what can wait (naming polish, optional abstractions, secondary refactors). Constructor injection in .NET and clear ownership in C++ are cheap habits that keep later change affordable.
  3. Describe how shortcuts stay visible: a ticket with the reason and a trigger for repaying it, a comment at the site, a note in review. Tracked, deliberate debt differs from accidental mess. Candidates describe addressing technical debt as part of this role, so include a time you paid some down.
  4. Give a result: a date met and a later change that stayed easy, or a shortcut that hurt and what you learned. Avoid absolutes such as always clean first or ship now and fix later.

Follow-up

  • How do you decide when a shortcut has to be repaid?
  • Describe a shortcut that backfired.
  • How do you explain the cost of technical debt to someone pushing for the date?