cswork / libraryOpen practice workspace

Interview guides · Betfred

Betfred Software Engineer Interview Guide

Betfred Software Engineer interview guide: reported rounds, .NET, API design, RabbitMQ and behavioral questions, with a 7-day prep plan.

The 60-second brief

Betfred is a gaming and betting brand. The Software Engineer role, as candidates describe it, centres on backend services built mainly on .NET, messaging systems that move real-time data between services, and workloads that must cope with heavy traffic. Engineers are described as owning features through their whole life cycle: writing code, reviewing colleagues' code, joining stand-ups and sprint planning, and working with product managers and other engineers to turn business requirements into services. The requirements listed are .NET proficiency, API design and database interaction, with RabbitMQ-style messaging, cloud-native development and experience in a high-volume transactional industry as extras.

This guide is for candidates preparing for a Software Engineer interview at Betfred. No take-home or coding platform is named in the reports.

Candidates report three rounds over roughly 3-5 weeks: a Recruiter Screen, Technical Discussions, then Executive Discussions. Timing and emphasis are said to vary with the team and the seniority of the role.

Focus areas

  • Backend development
  • .NET
  • API design

Overview

The role description candidates quote is a backend one. Day-to-day work is described as .NET services, messaging that carries real-time data, scalable and secure system design, code review and agile team rituals, with mentoring of junior engineers on top. That shapes preparation: you need working fluency in .NET async code, API design, and database access, plus enough message-broker knowledge to talk about RabbitMQ from experience rather than from a feature list.

The reported question set falls into three groups. The technical group covers API design for high throughput, test-driven development, asynchronous programming in .NET, database performance and RabbitMQ. The design and problem-solving group covers optimising a system for large data volumes, debugging production issues, scalable backend design, balancing features against stability, and long-term code structure. The behavioral group covers a tight deadline, technical disagreement, explaining a problem to a non-technical stakeholder, and motivation for the gaming and betting industry.

Candidates are advised to ground every answer in work they have actually done and to structure project stories with Situation, Task, Action, Result. Build a bank of four or five real projects first, then decide which one carries each question: one for a data-volume optimisation, one for a production incident, one for a disagreement, one for a deadline. Reuse the same project for a technical answer and a behavioral answer where it fits, so your detail stays consistent.

The domain matters at the later stages. Candidates describe real-time data processing and high availability as the challenges of betting and gaming platforms, and suggest following industry trends before the executive stage. Prepare a view on why those two constraints change engineering choices, such as how you would keep a service correct when messages repeat or a dependency is slow.

The Betfred interview process

Candidates report three stages, each broader than the last: an initial fit screen, in-depth technical discussions, and high-level executive discussions. Formats beyond those descriptions are not published, so ask the recruiter what to expect.

Recruiter Screen

The Recruiter Screen is described as an initial assessment of whether you fit the role. No technical depth is reported for it, so treat it as the stage where your background is matched against the listed requirements: .NET development, API design and database work, with messaging experience as a plus. Be ready to explain concisely what you built, with which technologies, and why this role.

What to demonstrate

  • Fit between your background and the role's listed requirements
  • How clearly you summarise your own experience
  • Practical details such as availability and what you want from the role

How to prepare

  • Write a short career narrative that names your .NET, API and database work and the scale of the systems involved
  • Mark each listed requirement (.NET, API design, database interaction, RabbitMQ, cloud-native) with one project of yours that proves it, and note gaps honestly
  • Prepare an answer on why you want to work in gaming and betting, since it is a reported question
  • Ask the recruiter for the remaining rounds, their format and who you will meet

Technical Discussions

Technical Discussions are described as in-depth evaluations of technical skills. Candidates report a mix of conceptual discussion and practical problem-solving rather than syntax recall. The technical questions reported for the loop cover API design for high throughput, TDD, async programming in .NET, database performance, RabbitMQ, production debugging, scalable backend design and maintainable code structure. Prepare each as an explanation with trade-offs and a concrete example from your own work.

What to demonstrate

  • Depth in .NET and backend development
  • API design and database interaction choices, with the reasoning behind them
  • Ability to explain trade-offs and why you picked one solution
  • Practical approach to production problems and testing

How to prepare

  • Rehearse an async answer: what await does at runtime, why blocking on a task can deadlock, how you pass cancellation tokens and cap parallel calls
  • Take one API you built and describe pagination, idempotency, rate limiting and versioning choices for a high-throughput version of it
  • Pull an execution plan for a slow query and explain the index or query change you made, and what you measured afterwards

Executive Discussions

Executive Discussions are described as high-level conversations about strategic thinking and team culture fit. Candidates also report that awareness of the betting and gaming industry helps here, particularly for senior roles. Expect questions on how you work with colleagues, handle feedback and think about trade-offs at product level, so keep technical detail in service of a decision.

What to demonstrate

  • Strategic thinking about trade-offs between features, stability and technical debt
  • Team fit: communication, collaboration and handling of feedback
  • Awareness of the betting and gaming industry and its real-time, high-volume demands

How to prepare

  • Prepare a two-sided story about balancing a feature deadline against stability, naming what you protected and what you postponed
  • Read current betting-industry news and write down two technical challenges you can link to real-time data and high availability
  • Rehearse explaining a complex technical problem to a non-technical listener using impact, options and a recommendation
  • Write questions about the team's current technical challenges and roadmap, which candidates are advised to ask

Where candidates lose points

Describing async and await as syntax, with no account of what happens at runtime

Explain that await frees the thread while I/O is pending, why blocking on a task with .Result or .Wait() can deadlock or starve the thread pool, and how cancellation tokens and capped parallel calls (for example a semaphore) keep a service stable.

Listing RabbitMQ features without any failure story

Tell one concrete case about acknowledgements, retries, dead-letter queues and idempotent consumers. Say plainly what you did not use, and state the equivalent concepts from any other broker you know.

Claiming a performance win with no baseline or measurement

Give the before and after number, the query plan or trace that located the bottleneck, and the load under which you measured. If you have no exact figure, say what you observed and how you checked.

Telling team stories in which your own decision is invisible

Use first person for the choices you made, name what you rejected and why, and credit teammates separately. Each Situation, Task, Action, Result story should have one decision the listener can point to.

Treating the industry question as small talk and skipping the domain

Be able to explain why real-time data and high availability change design choices on a betting platform, and have a thought-out answer to the motivation question that does not rely on slogans.

Questions & practice

Can you explain how you handle asynchronous programming in a.NET environment?

Medium · Technical & Domain Expertise

Approach

  1. Explain what await does: for I/O-bound work the thread goes back to the pool while the operation is pending, and the compiler-generated state machine resumes the method when the task completes. If a SynchronizationContext exists (UI apps, classic ASP.NET) the continuation runs on it. ASP.NET Core has none, and library code uses ConfigureAwait(false).
  2. Separate I/O-bound from CPU-bound work: await the async I/O APIs directly, and use Task.Run only to move CPU work off a UI thread. In ASP.NET Core, wrapping I/O in Task.Run just occupies a second pool thread.
  3. Name the anti-patterns and why they hurt: .Result or .Wait() blocks a thread, can deadlock under a single-threaded SynchronizationContext, and causes thread-pool starvation under load. async void is for event handlers only, because the caller cannot observe its exceptions.
  4. Control concurrency: Task.WhenAll for independent calls, SemaphoreSlim.WaitAsync or Parallel.ForEachAsync with MaxDegreeOfParallelism to bound fan-out, and Channel<T> with bounded capacity for producer-consumer backpressure. Pass a CancellationToken through every layer.
  5. Handle shared state correctly: you cannot await inside a lock statement (compiler error CS1996), so use SemaphoreSlim(1, 1) for an async critical section. ValueTask suits hot paths that often complete synchronously, but must not be awaited twice.

Follow-up

  • Why can .Result deadlock in WinForms or classic ASP.NET but usually not in ASP.NET Core?
  • Two of five tasks passed to Task.WhenAll throw. What does await surface, and how do you get every exception?
  • Latency climbs under load while CPU stays low. How would you confirm thread-pool starvation? (For example, dotnet-counters thread-pool queue length and thread count.)

Track a rolling failure rate per destination for circuit decisions

Easy · sliding window · ring buffer · circuit breaker

The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.

Approach

  1. Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
  2. Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
  3. State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
  4. Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.

Follow-up

  • The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
  • A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?

Canonicalise a request body into a stable idempotency fingerprint

Medium · parsing · canonicalisation · hashing

idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.

Approach

  1. Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
  2. Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
  3. Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
  4. Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.

Follow-up

  • A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
  • Where does the fingerprint get computed relative to request decompression and the body-size limit?

Find version gaps and relay lag with window functions

Medium · window functions · event tables · frame semantics

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

Approach

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

Follow-up

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

Hold a per-tenant active cap against concurrent creates

Hard · write skew · isolation levels · constraints

A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.

Approach

  1. Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
  2. Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
  3. Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
  4. State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.

Follow-up

  • A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
  • The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?

How do you approach API design in high-throughput environments?

Medium · Technical & Domain Expertise

Approach

  1. Start with the contract for writes that must not happen twice, such as a payment: the client sends an idempotency key, the server stores key plus result under a unique constraint, and a retry after a timeout returns the first result instead of writing again.
  2. Design list endpoints for volume: keyset (cursor) pagination such as WHERE (created_at, id) < (@lastCreated, @lastId) seeks an index, while OFFSET reads and discards every skipped row. Offer batch endpoints and field selection so clients do not make many chatty calls.
  3. Protect the service from its callers: per-client rate limits (token bucket) answered with 429 and Retry-After, timeouts on every downstream call, bounded concurrency, and load shedding with 503 so overload does not cascade.
  4. Use HTTP caching for read-heavy data (ETag with If-None-Match returning 304, Cache-Control max-age), and return 202 Accepted with a status resource when the work belongs on a queue rather than in the request.
  5. In .NET, keep the request path async end to end so threads are not blocked on I/O, create HttpClient through IHttpClientFactory to avoid socket exhaustion, and version the API with additive changes only, never repurposing an existing field.

Follow-up

  • The client timed out but your server committed the write. How does the retry avoid a duplicate?
  • Rows are inserted while a client pages through results. What does offset paging return, and how does a cursor fix it?
  • How would you ship a breaking change to an endpoint that many clients call?

What are your thoughts on Test-Driven Development (TDD) and its impact on code quality?

Medium · Technical & Domain Expertise

Approach

  1. Define the loop precisely: write a failing test, write the minimum code that passes it, then refactor while the tests stay green. Say what it changes in the code: it forces seams such as constructor-injected dependencies and small units with clear inputs and outputs.
  2. Give a balanced position, not a slogan: TDD pays most on business rules with clear expected results and on bug fixes (I first write a test that reproduces the bug, then fix it). It fits less well in exploratory spikes, which I throw away and then rebuild test-first.
  3. Place it in a test pyramid for .NET: many fast unit tests (xUnit or NUnit, with Moq or NSubstitute for collaborators), fewer integration tests using WebApplicationFactory and a real database in a container, and a small number of end-to-end checks.
  4. Name the pitfalls: over-mocking ties tests to the implementation so every refactor breaks them, and line coverage says nothing about whether assertions catch defects. Assert observable behaviour, and use mutation testing (Stryker.NET) to check whether the tests detect changed code.
  5. Close with evidence: one concrete case where a test suite caught a regression or made a large refactor safe, and what it cost to maintain.

Follow-up

  • How do you test-drive code that depends on the current time or on randomness? (For example, inject TimeProvider in .NET 8 or a clock interface.)
  • You inherit a large class with no tests. Where do you start? (For example, characterization tests that pin current behaviour before changing it.)
  • When has writing tests first slowed you down, and what did you do about it?

How do you ensure database interactions are optimized for performance?

Medium · Technical & Domain Expertise

Approach

  1. Measure before tuning: find the expensive queries (SQL Server Query Store or actual execution plans, EXPLAIN ANALYZE in PostgreSQL) and compare logical reads and row estimates against actual rows, not just elapsed time.
  2. Index for the access path: a composite index with equality columns first and the range or sort column last, plus a covering index (INCLUDE columns) to remove key lookups. Keep predicates sargable (no function wrapped around an indexed column, no leading-wildcard LIKE). Every index also costs write throughput and storage.
  3. Fix how the application talks to the database: remove ORM N+1 patterns (Entity Framework Core lazy loading inside a loop) with Include or a Select projection, use AsNoTracking for read-only queries, select only the columns needed, batch writes, and use parameterized queries so plans are reused.
  4. Manage connections and transactions: ADO.NET pools connections per connection string by default, so open late and dispose early. Keep transactions short, never hold one open across a remote call, choose the isolation level deliberately, and use async database calls.
  5. Go beyond a single query when volume demands it: cache-aside for read-heavy data with an invalidation plan, read replicas (with their replication lag), keyset pagination, and partitioning or archiving tables that grow without bound.

Follow-up

  • A query is fast in SSMS but slow from the application. What could explain it? (For example, different SET options such as ARITHABORT produce a separate cached plan, or parameter sniffing.)
  • How would you add an index to a large table that takes writes all the time without blocking them?
  • When would you denormalize, and what keeps the copies consistent?

What are the key considerations when designing a scalable backend service?

Medium · System Design & Problem Solving

Approach

  1. Pin the numbers first: peak requests per second, read/write ratio, latency target, availability target and data growth. Say how traffic spikes change the design compared with a steady average.
  2. Keep the application tier stateless behind a load balancer so instances scale horizontally. Session or shared state goes in a distributed cache such as Redis, and autoscaling keys off CPU, latency or queue depth.
  3. Treat the data tier as the usual bottleneck: read replicas, cache-aside with TTLs and stampede protection, and sharding by a key that spreads load (watch for hot keys). Choose consistency per operation: an account balance needs transactional consistency, while a feed or leaderboard can tolerate staleness.
  4. Move work that does not need to finish inside the request onto a message broker such as RabbitMQ. Delivery is at-least-once, so consumers must be idempotent, and the transactional outbox pattern commits the database change and the event together.
  5. Design for failure and visibility: timeouts, retries with exponential backoff and jitter, circuit breakers and bulkheads (Polly in .NET), health checks, rate limiting, graceful degradation, and metrics, traces and logs from the start.

Follow-up

  • A hot cache key expires and thousands of requests hit the database at once. How do you prevent it?
  • How do you keep a database write and the message you publish consistent without a distributed transaction?
  • At ten times today's traffic, which component fails first, and how would you know before it does?

How do you balance the need for new features with the need for system stability?

Medium · System Design & Problem Solving

Approach

  1. Make the trade-off measurable: define SLOs and an error budget (a 99.9% success SLO allows 0.1% failed requests over the window). While budget remains, feature work ships; when it is spent, reliability work takes priority. This turns an argument into a rule agreed in advance.
  2. Lower the risk of each change rather than slowing change down: feature flags separate deploy from release, canary or staged rollouts roll back automatically on metric regression, pull requests stay small, and database migrations are backward compatible (expand, migrate, then contract).
  3. Put the gates in the pipeline: unit and integration tests, static analysis, and load tests on the hot paths before a release, so stability does not depend on someone remembering to check.
  4. Fund technical debt explicitly: reserve a share of each iteration, and rank debt items by incident and toil data so the case to product is evidence, not preference.
  5. Back it with a story from experience: a time I reshaped or delayed a feature for stability (or shipped it behind a flag), the data I used, and what happened afterwards.

Follow-up

  • Product wants a launch before a peak period, but the error budget is already spent. What do you do?
  • How do you stop feature flags from piling up into their own maintenance problem?
  • How would you explain to a non-technical stakeholder why a refactor should come before a feature?

How do you structure your code to ensure it remains maintainable over the long term?

Medium · System Design & Problem Solving

Approach

  1. Structure by boundaries: domain logic free of framework and infrastructure code, dependencies pointing inward (clean or hexagonal architecture), and databases, brokers and HTTP clients behind interfaces registered in the .NET DI container. Mention vertical slices by feature as the alternative and when each fits.
  2. Apply SOLID with concrete examples rather than reciting it: single responsibility for classes that change for one reason, dependency inversion so units can be tested. Avoid speculative abstraction too: an interface with one implementation needs a reason, such as a test seam.
  3. Let tooling enforce the conventions: .editorconfig with Roslyn analyzers, nullable reference types enabled, warnings treated as errors in CI, consistent naming, and code review focused on design and readability, not formatting.
  4. Treat tests as what keeps refactoring safe, and record significant decisions as short architecture decision records so the reasons outlive the people who made them.
  5. For legacy code: add characterization tests first, refactor in small steps, replace large parts incrementally with the strangler fig pattern, and leave each file you touch a little cleaner.

Follow-up

  • What signals tell you a codebase is becoming hard to maintain? (For example, change failure rate, churn hotspots, complexity, onboarding time.)
  • When would you choose vertical slices over a layered structure?
  • How would you safely break up a class with thousands of lines that has no tests?

How do you approach debugging complex issues in a production environment?

Medium · System Design & Problem Solving

Approach

  1. Stabilize before diagnosing: size the impact (which users, which endpoints, error rate), then mitigate by rolling back the latest deploy, turning off a feature flag, scaling out or shedding load, and keep stakeholders updated while you do it.
  2. Build the timeline: what changed and when (deploys, config, traffic volume, a dependency, the shape of the data). Line up the onset in the metrics with the change log, and treat the change as the trigger, which is not always the root cause.
  3. Localize with telemetry: request rate, errors and duration per service to find where it hurts, distributed traces (a correlation or trace ID, for example via OpenTelemetry) to find the slow hop, then structured logs filtered by that ID. Slice by instance, region, endpoint and customer to see whether one dimension isolates it.
  4. Test the cheapest hypothesis that rules out the most options, and reproduce with production-like data in a lower environment. For .NET process issues, use dotnet-counters (GC, thread pool, exception rate), dotnet-trace for CPU, and dotnet-dump or dotnet-gcdump for hangs and memory growth.
  5. Close the loop: ship the fix, add the test and the alert that would have caught it earlier, and write a blameless postmortem with owned action items.

Follow-up

  • The error is intermittent and you cannot reproduce it locally. What do you add or look at next?
  • A .NET service's memory grows until it is restarted. How do you find the leak? (Compare heap snapshots; look for static collections, unremoved event subscriptions, unbounded caches.)
  • How do you decide between rolling back and shipping a forward fix?

Suggested practice checklist

Day 1 — Map the loop and build a story bank

  • Write the three reported rounds (Recruiter Screen, Technical Discussions, Executive Discussions) and note what each is described as covering
  • List four or five of your projects and match each to a reported prompt: data-volume optimisation, production incident, deadline, disagreement, stakeholder explanation
  • Mark each listed requirement (.NET, API design, database interaction, RabbitMQ) with your strongest evidence and your largest gap

Deliverable: A one-page round map plus a table pairing each project with the prompts it can answer and the requirement gaps to close.

Day 2 — Asynchronous programming in .NET

  • Write a small C# endpoint that calls two dependencies concurrently with Task.WhenAll, a cancellation token and a timeout
  • Reproduce the .Result deadlock and thread-pool starvation, then fix it with end-to-end async
  • Say aloud how you handle asynchronous programming in a .NET environment, covering I/O versus CPU work, ConfigureAwait and limiting parallelism

Deliverable: A working C# sample with a cancellation path, plus a spoken answer that covers runtime behaviour and the failure modes.

Day 3 — API design and scalable backend services

  • Design the API for a high-throughput write path: idempotency keys, pagination, rate limits, versioning and error contracts
  • Sketch a scalable backend service separating read and write paths, with caching, queues and a stated consistency model

Deliverable: One API specification, one service diagram with named bottlenecks, and two solved practice drills.

Day 4 — Database interactions and performance

  • Take a slow query, read its execution plan, add or change an index and record before and after timings
  • List your checklist for database access from .NET: N+1 queries, batching, connection pooling, transaction scope, keyset pagination

Deliverable: A before/after query-plan note, a database performance checklist, and two solved SQL drills.

Day 5 — RabbitMQ, TDD and maintainable code

  • Build a producer and consumer with manual acks, a prefetch limit, retries and a dead-letter queue, then kill the consumer mid-message
  • Write a message handler test-first and note where TDD changed your design
  • Prepare a refactoring walkthrough for legacy code: characterisation tests, small steps, seams

Deliverable: A working messaging sample with a failure-test note, a test-first handler, and spoken answers on RabbitMQ experience, TDD and long-term code structure.

Day 6 — Production debugging, data volume and stability

  • Write a production-debugging runbook: what changed, symptoms, metrics, logs and traces, bisect, mitigate, then root cause
  • Choose your best data-volume optimisation story and fill in baseline, bottleneck, change, result
  • Prepare a framework for features versus stability and technical debt, with the signals that would make you pause feature work

Deliverable: A one-page debugging sequence, a completed optimisation story with numbers, and a short decision framework for features versus stability.

Day 7 — Executive and behavioral rehearsal

  • Answer aloud the tight-deadline, technical-disagreement and non-technical-stakeholder prompts using Situation, Task, Action, Result
  • Write and rehearse your answer on what motivates you about gaming and betting, including a view on customer impact
  • Read recent betting-industry news and write questions to ask about the team's technical challenges and roadmap

Deliverable: Four recorded or written behavioral answers, a motivation answer, and a list of questions for the executive stage.

Behavioural preparation

Behavioral prompts reported for this loop combine team skills with engineering judgment: delivery under pressure, handling technical disagreement, explaining a hard problem to non-engineers, and motivation for the sector. Candidates are advised to use Situation, Task, Action, Result with specific actions and outcomes. Prepare each story around a decision you made and its measurable effect, rather than a team narrative.

  1. Describe a time a production system failed; what was your role in the resolution?

What is your experience with messaging systems like RabbitMQ?

Medium · Technical & Domain Expertise

Approach

  1. Open with one system you owned: what produced messages, what consumed them, and what broke if a message was lost or repeated. If your exposure is small, say so and describe it accurately; a precise small example beats vague breadth.
  2. Cover RabbitMQ through decisions you made: exchange type (direct, topic or fanout) and the routing-key scheme, durable queues with persistent messages, and publisher confirms so a producer knows the broker has accepted a message.
  3. Describe consumer behaviour: manual acknowledgements instead of auto-ack, a prefetch limit so one slow consumer does not hoard messages, and competing consumers for throughput, with the cost that ordering is no longer guaranteed across them.
  4. Explain failure handling: delivery is at-least-once, so consumers must be idempotent (dedupe on a message id or write with an upsert), and a message that keeps failing goes to a dead-letter exchange after bounded retries instead of looping.
  5. If an event follows a database write, mention the outbox pattern so the row and the event cannot disagree, and say how you monitored queue depth, unacked counts and consumer lag.

Follow-up

  • A consumer crashes after doing the work but before acknowledging. What happens, and how does your code stay correct?
  • How do you stop a poison message from blocking a queue, and how do you replay dead-lettered messages safely?
  • When would you choose a database table or a log-based broker instead of RabbitMQ?

Describe a time you had to optimize a system to handle large volumes of data.

Medium · System Design & Problem Solving

Approach

  1. Pick a case where you made the call, and begin with the baseline: data volume, request or row rates, and the metric that hurt (p95 latency, batch run time, queue backlog). Without a before number the result cannot be checked.
  2. Show how you found the bottleneck: profiling, query plans, slow-query logs or traces, and which hypothesis you discarded. Name the real cause, such as a missing index, row-by-row inserts, an unbounded query or a chatty service call.
  3. Describe the change and its alternatives: batching or bulk inserts, indexing or partitioning, keyset pagination, caching, moving work onto a queue, or archiving cold data. Say what each cost in complexity or staleness.
  4. Close with the measured result under the same load, how you guarded against regression (alert, load test, dashboard), and what you would do at the next tenfold growth.

Follow-up

  • How did you prove the bottleneck was where you thought it was before changing code?
  • What trade-off did the fix introduce, such as staleness or operational complexity?
  • What would break first if the data grew another tenfold?

Tell us about a time you had to meet a tight deadline under pressure.

Medium · Behavioral & Team Dynamics

Approach

  1. Choose a deadline with real consequences and state it in one or two sentences, then spend the answer on your decisions: how you cut scope, what you sequenced first, and what you refused to cut.
  2. Show that you protected the risky path: tests on the money or data-integrity code, a rollback or feature flag, and a release that was reversible.
  3. Show communication: when you told stakeholders about the trade-off, what options you gave them, and what they agreed to drop.
  4. Say what happened afterwards: any shortcut you took, how it was recorded, and when it was repaid. The weak version is long hours and heroics with no judgment and no later cleanup.

Follow-up

  • What exactly did you cut, and who agreed to cut it?
  • Did the shortcut cause a problem later, and how did you handle it?
  • What would you change so the same crunch does not happen again?

How do you handle disagreements regarding technical direction within your team?

Medium · Behavioral & Team Dynamics

Approach

  1. Pick a disagreement between two defensible options, such as a queue versus a direct call, or a framework choice, and state the other person's strongest argument fairly before your own.
  2. Show how you moved from opinion to evidence: a small spike, a benchmark, a written design note with decision criteria, or a review with a third engineer.
  3. Describe the outcome honestly, including a time you changed your mind or lost. Disagree-and-commit works as a conclusion only if you then delivered the agreed direction properly.
  4. Avoid the story where you were simply right and the colleague was difficult; it shows no collaboration. Say what the relationship looked like afterwards.

Follow-up

  • What if the data had not settled it? How would the decision have been made?
  • Tell me about a time you were the one who was wrong.
  • How do you handle a disagreement with someone more senior than you?

Describe a situation where you had to explain a complex technical problem to a non-technical stakeholder.

Medium · Behavioral & Team Dynamics

Approach

  1. Choose a real problem, such as an outage cause, a performance limit or a technical-debt trade-off, and name the person and the decision they had to make. The story is about that decision, not about the technology.
  2. Translate to their terms: customers affected, money or risk, time to fix. Use one accurate analogy and drop internal names, protocols and acronyms.
  3. Offer two or three options with cost and risk each, give your recommendation and say what you need from them.
  4. Check understanding by asking them to restate the decision, and follow up in writing. The weak version is simplifying so far that nothing was decided, or burying them in detail.

Follow-up

  • How did you know they understood the risk and not just the words?
  • What did you do when they pushed for a date you thought was unrealistic?
  • Which numbers or visuals did you show, and which did you leave out?

What motivates you to work in the gaming and betting industry?

Medium · Behavioral & Team Dynamics

Approach

  1. Give an honest, specific reason that connects to engineering: for example real-time data, bursty traffic around events, high availability, or correctness when money is involved. These are the challenges candidates are advised to read up on for betting and gaming platforms.
  2. Support it with your own evidence: a project where you handled spikes, idempotent payments or low-latency messaging, or the reading you did on the sector's technical problems.
  3. Show a considered view of the product's impact on customers rather than ignoring the ethics of betting. A short, calm, honest position is better than avoidance.
  4. Avoid money as the main motive, generic phrases about a fast pace, and invented fandom or specifics about the company that you cannot back up.

Follow-up

  • How do you think about the responsibility that comes with building betting products?
  • Which technical challenge in real-time betting platforms interests you most, and why?
  • What would you want to learn in the first months in this domain?