Interview guides · BondR
BondR Software Engineer Interview Guide
BondR Software Engineer interview guide: four reported rounds, Java/.NET depth, system design and behavioral prompts, and a 7-day prep plan.
The 60-second brief
BondR builds financial technology products, and candidates describe the Software Engineer role as backend engineering: services written in Java or .NET, plus cloud infrastructure work. Reported responsibilities include taking a feature from design through deployment, reviewing teammates' code, joining architectural planning, moving older components onto cloud architectures, tuning the performance of services that already exist, and adding new features. Candidates also describe regular work with product managers and operations colleagues.
This guide is for Software Engineer candidates whose loop centres on Java or .NET backend work. It covers the four reported rounds, the fifteen reported questions (five on language depth, five on system design and cloud, five behavioral), the evaluation areas candidates list, and a seven-day plan that turns them into practice.
Candidates report four rounds over roughly 3-5 weeks: Initial Screen, Technical Assessments, Team Interactions and Final Review. Coding exercises and architectural discussions are reported in the technical stage.
Focus areas
- Java
- C#
- .NET Framework /.NET
Overview
The stack candidates describe is Java or .NET for services, relational databases, and RESTful API design, with AWS, Azure or GCP, microservices orchestration and infrastructure-as-code listed as extras. The topic breakdown for the loop names Java, C#, .NET, cloud computing and object-oriented programming. In practice, choose the one language you know best and prepare it down to runtime behaviour: where objects live, how garbage collection reclaims them, which concurrency primitives you reach for and why.
The reported questions fall into three groups. Language depth covers heap versus stack, concurrency in high-throughput code, unit testing and coverage, dependency injection, and memory use on large datasets. Design covers a highly available service, SQL versus NoSQL for a new microservice, service-to-service communication and API security. The behavioral group asks for stories about explaining a decision to a non-technical person, changing approach, architectural disagreement, a production incident and your hardest technical challenge. Candidates also list evaluation areas to review: garbage collection, thread safety, indexing and query tuning, CI/CD, Docker and Kubernetes, serverless, load balancing, caching, event-driven versus request-response designs, the CAP theorem, circuit breaking and distributed transactions.
Two reported tips shape how to practise. Candidates are told to be ready to talk about distributed systems and cloud-native architecture from their own experience, and to write readable, modular code during assessments. So rehearse by speaking: state constraints and expected traffic before designing, narrate your reasoning, and say plainly when a tool is new to you and how your existing knowledge applies.
The role's reported work (migrating legacy components to the cloud, improving the performance of running services, shipping features alongside product and operations colleagues) maps directly onto the behavioral prompts. One strong story about a slow service, one about a migration or changed requirement, and one about an incident can each be reshaped to answer several prompts, so build a small story bank early rather than inventing answers per question.
The BondR interview process
Candidates report four stages over about 3-5 weeks, described only by name and a one-line format. The preparation below is organised around what each format inherently requires.
Initial Screen
Candidates describe the Initial Screen as a conversation about your background and your interest in the role. Treat it as the first telling of the story you will repeat later: your primary language (Java or .NET), the backend and cloud work you have done, and why the reported responsibilities (cloud migration, performance work, shipping features with product and operations colleagues) fit what you want next.
What to demonstrate
- How clearly you describe your own background and projects
- Your stated interest in the role and its reported responsibilities
- Which of Java or .NET you work in, since the loop is reported to vary by stack
How to prepare
- Write a short career narrative anchored on services you built, the databases they used and any cloud or migration work.
- Pick two projects with a concrete technical challenge each; they become your behavioral stories in later rounds.
- Link your interest to specific reported work, such as moving legacy components to the cloud or tuning existing service performance, instead of a general statement about fintech.
Technical Assessments
Candidates report coding exercises and architectural discussions in the Technical Assessments stage. No specific coding problems are reported, so prepare the general ability: small, readable, testable code in your primary language, plus the language-depth and design topics in the reported question list (heap and stack, concurrency, unit tests, dependency injection, memory on large datasets, a highly available service, SQL versus NoSQL, service communication, API security).
What to demonstrate
- Working, readable code in Java or C#, which is what a live coding exercise requires
- Command of language internals such as memory management and concurrency
- Reasoning about architectural trade-offs such as synchronous versus asynchronous processing, caching and database choice
How to prepare
- Practise writing a small class with constructor-injected dependencies and unit tests that use fakes, while narrating naming and structure choices.
- Prepare a concurrency toolkit in your language: locks, concurrent collections, atomics and immutable data, and when you would pick each.
- Walk through a highly available service design aloud, asking about constraints and expected traffic first, then covering load balancing, caching, failure handling and the database choice.
Team Interactions
Team Interactions is described as meeting various team members to check alignment with engineering standards and culture. The format is conversational, so expect to discuss how you work with other engineers: code review, testing habits, handling architectural disagreement, and working with product and operations colleagues. The behavioral prompts about disagreement, explaining decisions and changing course are the natural practice material.
What to demonstrate
- Alignment with the team's engineering standards as the stage is described
- How you collaborate with engineers and cross-functional colleagues
- How you explain the reasoning behind your technical choices
How to prepare
- Write out your own position on code review, test coverage and readable code, with one example of each from past work.
- Rehearse the architectural-disagreement story and the non-technical-stakeholder story until the decision and its trade-off come first.
- Prepare questions about how the team reviews code, tests and deploys, so the conversation runs in both directions.
Final Review
Candidates describe the Final Review as a stage that assesses overall fit and expertise. Since no format beyond that is reported, prepare to cover everything you have said so far: your strongest technical areas, your stories, and the stack you named in the first round, with enough depth to revisit any of it.
What to demonstrate
- Overall fit for the role, as reported
- Breadth and depth of expertise across the stack you described
- Consistency between the experience you described early and the detail you can give now
How to prepare
- Keep a one-page sheet of your five strongest stories and the technical topics you are least sure of, and review it before this stage.
- Be ready to revisit an earlier design with a changed constraint, such as ten times the traffic or a downstream service being unavailable.
- Prepare a few questions about the team's migration and performance work to close with.
Where candidates lose points
Reciting textbook definitions of heap and stack or garbage collection without tying them to your language's runtime
Answer in terms of your actual stack: what lives in per-thread stack frames versus the heap, how the collector reclaims objects, and how value and reference types differ in Java or C#, with a concrete case such as a large object that stays reachable and causes memory growth.
Presenting a highly available service as a list of components with no account of what happens when each one fails
For every box on the diagram, say what happens when it is down or slow: redundant instances behind a load balancer, cache misses, a database failover, a downstream timeout handled by a circuit breaker. Finish with what breaks first at ten times the traffic.
Choosing between SQL and NoSQL on preference instead of access patterns
Start from the data: query shapes, need for transactions and joins, consistency requirements and expected growth. The role lists relational databases as a core skill, so be ready to explain when a relational store is still the right answer for a new microservice and what you would give up.
Behavioral stories where the decision, the measurement or your own part is missing
For the cloud performance story, the incident story and the hardest-challenge story, state your decision, the evidence you used and a before-and-after result. Use 'I' for what you did and name the team separately.
Writing code in the technical stage that works but is hard to read or test
Candidates report that readable, modular, tested code matters. Use clear names, small methods, injected dependencies and say how you would unit test it, narrating those choices as you write.
Questions & practice
Explain the difference between heap and stack memory management in your primary language.
Medium · Technical Proficiency and Language Depth
Approach
- In Java, each thread gets its own stack of frames holding primitive locals and references, while every object and array lives on the shared heap and the garbage collector reclaims it. Treat 'new means heap' as the language model rather than a guarantee: the HotSpot JIT can scalar-replace an object that never escapes its method.
- In C#, correct the myth that value types always live on the stack. A struct local usually sits in the frame, but a struct field of a class lives inline inside that heap object. Boxing copies a value type onto the managed heap, and locals captured by a lambda, iterator or async method get hoisted into a heap-allocated closure or state machine.
- Contrast the costs. Stack allocation is a pointer bump that is freed automatically on return, but the stack has a fixed size per thread: deep recursion throws StackOverflowError in Java, and in .NET a StackOverflowException cannot be caught and kills the process. Heap allocation is cheap in a generational GC, but it adds collection work and pause time.
- Tie this to failure modes you have seen: OutOfMemoryError: Java heap space, .NET's Large Object Heap for objects of 85,000 bytes or more and the fragmentation it causes, and leaks in a GC language from static collections, caches with no eviction, or event handlers that were never unsubscribed.
- Finish with the concurrency angle. Stack locals are confined to one thread, so they are safe. Anything on the heap that two threads can reach needs synchronization or immutability.
Follow-up
- What happens to memory when you add ints to a List<object> in C#, or to a List<Integer> in Java?
- A recursive tree walk crashes on a deep input. How would you rewrite it, and what does that cost?
- How would you show that a GC-managed service is leaking, and how would you find the object that holds the memory?
How do you handle multi-threading and concurrency issues in high-throughput applications?
Medium · Technical Proficiency and Language Depth
Approach
- Start by removing shared mutable state where you can: immutable value objects, thread confinement, or handing work over a queue. Only then reach for thread-safe building blocks such as ConcurrentHashMap or ConcurrentDictionary and atomics like AtomicLong or Interlocked, before writing explicit locks.
- Make compound operations atomic. A check-then-act on a concurrent map still races, so use computeIfAbsent or merge in Java, or GetOrAdd and AddOrUpdate in .NET. Note that GetOrAdd's value factory can run more than once under contention, so it must have no side effects.
- When you do lock, keep the critical section small, acquire locks in one global order to prevent deadlock, and use timed acquisition (tryLock, Monitor.TryEnter) where a hang would hurt. In Java, volatile gives visibility and ordering but not atomicity, so count++ on a volatile field is still a race.
- Bound concurrency: use a fixed-size pool with a bounded queue and an explicit rejection policy rather than unbounded thread creation. For I/O-bound .NET code, use async/await end to end and never block on .Result or .Wait(), which can deadlock or starve the thread pool. In Java 21, virtual threads are an option for blocking I/O.
- Say how you verify it: stress tests, jcstress for Java memory-model cases, thread dumps (jstack, dotnet-dump) to read deadlocks and contention, and lock-contention and pool-saturation metrics in production.
Follow-up
- Write a thread-safe counter, or a per-key rate limiter. Which primitive did you choose, and why?
- A service hangs under load with no errors. How do you tell a deadlock from thread-pool starvation?
- Under heavy write contention on one record, when would you choose optimistic concurrency (CAS or version checks) over a lock?
Describe your approach to writing unit tests and ensuring high code coverage.
Medium · Technical Proficiency and Language Depth
Approach
- Test behaviour through the public surface, not private methods. Use the Arrange-Act-Assert shape, give each test one reason to fail, and name it after the scenario and the expected outcome. Name your tools: JUnit 5 or xUnit/NUnit, with Mockito or Moq for collaborators.
- Replace only real boundaries with test doubles: the database, HTTP clients, the message bus and the clock. Inject time (java.time.Clock, or TimeProvider in .NET 8) so that date logic is deterministic. Over-mocking couples a test to the implementation, and the test then breaks on every refactor.
- Cover the edges on purpose: null and empty inputs, boundary values, rounding and currency precision where money is involved, and every error path. Use parameterized tests (@ParameterizedTest, or [Theory] with [InlineData]) so a table of cases replaces copy-pasted tests.
- Treat coverage as a signal, not a target. Line coverage (JaCoCo, coverlet) shows what ran, not what was asserted. Branch coverage is stricter, and mutation testing (PIT, Stryker.NET) checks whether the tests catch injected bugs. A threshold on changed code in CI is more useful than chasing 100% overall.
- Place unit tests in a pyramid: many fast, isolated unit tests; fewer integration tests against a real database (for example Testcontainers) for SQL and repository code; and contract tests between services. Keep all of them deterministic, with no sleeps, no shared state between tests and no real network.
Follow-up
- Coverage is at 90% but bugs still ship. What would you look at first?
- How would you unit-test code that calls DateTime.Now or a static helper you cannot change?
- How do you test code that runs on several threads without writing a flaky test?
What are the advantages of using dependency injection in a large-scale system?
Medium · Technical Proficiency and Language Depth
Approach
- Define it precisely: a class receives its collaborators, usually through the constructor and typed as interfaces, instead of constructing them itself. A composition root (the Spring container, or ASP.NET Core's IServiceCollection) wires the object graph once.
- List the concrete benefits. You can substitute fakes in tests, swap implementations per environment (a real payment gateway against a sandbox one), keep object lifetimes in one place, and add cross-cutting behaviour such as logging, retries or caching through decorators without editing the class.
- Argue for constructor injection over field or setter injection: dependencies are explicit, can be final or readonly, and the object can never exist half-built. A constructor with many parameters is a sign the class has too many responsibilities.
- Know the lifetimes and the classic bug. ASP.NET Core has Singleton, Scoped and Transient; Spring beans default to singleton, with prototype and request scopes available. A singleton that captures a scoped dependency (for example an EF Core DbContext, which is not thread-safe) is a captive dependency. ASP.NET Core's scope validation, on by default in Development, throws for it.
- Name the costs: wiring errors surface at startup or at first resolve instead of compile time, the indirection makes code harder to follow, and calling the container from inside business code (service locator) brings back the hidden dependencies DI was meant to remove. Since Spring Boot 2.6, circular references are prohibited by default and the application fails at startup.
Follow-up
- What is the difference between dependency injection and a service locator, and why does it matter for testing?
- A singleton background worker needs a scoped service. How do you give it one safely in .NET or Spring?
- Two services depend on each other through their constructors. How do you break the cycle?
How do you optimize memory usage when processing large datasets?
Medium · Technical Proficiency and Language Depth
Approach
- Stream instead of loading everything, so memory grows with the batch size rather than the dataset. In Java, read files with a BufferedReader or Files.lines and set a JDBC fetchSize; the PostgreSQL driver honours fetchSize only with autocommit off. In .NET, use a DataReader or IAsyncEnumerable rather than ToList() on the whole result.
- Push work to where the data lives. Select only the columns you need, and aggregate, filter and join in SQL rather than pulling rows into the application. Paginate with keyset pagination (WHERE id > last_id ORDER BY id LIMIT n) instead of a large OFFSET.
- Choose compact representations. A boxed Integer on 64-bit HotSpot is about 16 bytes plus a reference, against 4 bytes for an int in a primitive array. Pre-size collections, and avoid repeated string copies. In .NET, Span<T> and ArrayPool<T> cut allocations, and reusing buffers avoids churn on the Large Object Heap.
- When the data cannot fit in memory, use out-of-core algorithms: external merge sort (sort chunks to disk, then k-way merge with a heap), hash-partitioning to disk before a group-by or join, and approximate structures such as HyperLogLog for distinct counts or a Bloom filter for membership when exact answers are not required.
- Measure before and after: heap dumps (jmap, dotnet-gcdump) read through the dominator tree and retained sizes, GC logs for pause time and promotion rate, and a deliberate heap limit (-Xmx or a container memory limit), so the job fails fast in testing rather than in production.
Follow-up
- Count the distinct user ids in a 100 GB file on a machine with 1 GB of RAM. What do you do if the answer must be exact?
- Sort a file larger than memory, and give the I/O cost of your approach.
- A batch job's memory climbs steadily until it is killed. How do you find out what is retaining the objects?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
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
- 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.
- 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.
- 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.
- 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?
How would you design a highly available service that handles millions of requests per day?
Medium · System Design and Cloud Architecture
Approach
- Turn the load into numbers first: 1 million requests a day averages about 11.6 per second, and 10 million about 116, so even with a 3-10x peak factor the throughput is modest. Availability is the real requirement, so state the target: 99.9% allows about 43.8 minutes of downtime a month, and 99.99% about 4.4 minutes.
- Keep the application tier stateless behind a load balancer, spread across at least two availability zones, with health checks and autoscaling. Move session and other shared state to an external store, so that losing any single instance is invisible to callers.
- Make the data tier survive a node loss: a primary with a replica in another zone and automatic failover, read replicas for read-heavy paths, and a cache-aside layer (for example Redis) with TTLs. State the RPO and RTO you are designing for, and whether replication is synchronous (no data loss, slower writes) or asynchronous.
- Add resilience at every call boundary: timeouts, retries with exponential backoff and jitter for idempotent operations only, idempotency keys so a retried money-moving write is applied once, circuit breakers, per-client rate limits, and a queue for work that does not have to finish inside the request.
- Close with operations: rolling, blue-green or canary deploys with automatic rollback; expand-then-contract schema migrations; monitoring of latency, traffic, errors and saturation, with alerts on SLO burn rate. Mention multi-region active-passive as the next step, along with its replication-lag and cost trade-offs.
Follow-up
- The primary database fails mid-request. What does the client see, and can a write be lost or applied twice?
- A dependency slows down and every client starts retrying. How do you stop a retry storm?
- How would you change a column type on a busy table without downtime?
What factors do you consider when choosing between SQL and NoSQL databases for a new microservice?
Medium · System Design and Cloud Architecture
Approach
- Start from the access patterns and invariants, not the brand. If the service needs multi-row ACID transactions, joins, foreign keys or ad-hoc reporting queries (ledgers, balances, orders), a relational database such as PostgreSQL or SQL Server is the default choice.
- Make the case for NoSQL in concrete terms: key-based access at high write volume, built-in horizontal partitioning, or a document shape that varies per record. Then name the cost. In DynamoDB or Cassandra you design tables around known queries, and a poor partition key creates hot partitions while new query patterns need new tables or indexes.
- Be exact about consistency rather than repeating slogans. DynamoDB reads are eventually consistent by default, with strongly consistent reads available on request. Cassandra lets you tune consistency per query (for example QUORUM). MongoDB has supported multi-document transactions since 4.0. 'NoSQL has no transactions' is not accurate.
- Cover scaling honestly. A relational database scales reads with replicas and writes vertically for a long way before sharding is needed, so for most services expected volume alone does not justify NoSQL. Weigh the operational factors too: managed offerings, backup and restore, schema migrations, team familiarity and cost.
- Address the microservice part: each service owns its store and no other service reads its tables directly. That allows polyglot persistence, but it pushes cross-service consistency into patterns such as the transactional outbox and sagas.
Follow-up
- Given these access patterns, how would you model the data in a key-value store, and which query becomes expensive?
- Two services each own a database and an operation must update both. How do you keep them consistent?
- What signal would make you move an existing service off a relational database?
How do you manage service-to-service communication in a microservices architecture?
Medium · System Design and Cloud Architecture
Approach
- Separate the two styles. Use synchronous calls (REST over HTTP, or gRPC with protobuf contracts over HTTP/2) when the caller needs the answer to continue. Use asynchronous messaging (Kafka, RabbitMQ, SQS or Azure Service Bus) for events and commands, so that one service's outage does not block another.
- Harden every synchronous call: a timeout on each request, retries with exponential backoff and jitter only for idempotent operations, a circuit breaker (Resilience4j in Java, Polly in .NET), and bulkheads, so that one slow dependency cannot use up every thread or connection.
- For messaging, assume at-least-once delivery and make consumers idempotent by deduplicating on a message id. Rely on ordering only within a partition key (Kafka keeps order per partition), and send poison messages to a dead-letter queue with alerting.
- Avoid the dual write. Updating your database and publishing an event separately can lose one of them, so use a transactional outbox: write the event to an outbox table in the same transaction and have a relay publish it. Coordinate multi-service workflows with sagas and compensating actions rather than two-phase commit.
- Cover the plumbing: service discovery (Kubernetes DNS, Consul), mTLS between services (often through a mesh such as Istio or Linkerd), propagation of trace and correlation ids with OpenTelemetry, and contract versioning with backward-compatible changes, a schema registry or consumer-driven contract tests such as Pact.
Follow-up
- The downstream service is not down, just slow. What happens to your service, and what protects it?
- How do you guarantee an event is published if and only if the database commit succeeded?
- A consumer receives the same payment event twice. How does your design make sure the account is only charged once?
Explain the security best practices you implement when exposing APIs to public traffic.
Medium · System Design and Cloud Architecture
Approach
- Secure transport and identity first: TLS on every hop, with HSTS for browser clients, and OAuth 2.0 or OpenID Connect for users. For JWT access tokens, keep lifetimes short and validate the signature, issuer, audience and expiry; pin the accepted algorithms, never accept 'none', and rotate signing keys. Server-to-server keys need rotation and a scope.
- Authorize every object, not just every endpoint. Broken Object Level Authorization (an IDOR, where changing an id in the URL returns another user's record) is first on the OWASP API Security Top 10 (2023). Deny by default, give tokens least-privilege scopes, and check ownership in the service, not only at the gateway.
- Treat all input as hostile. Validate requests against a schema with size limits, bind SQL values as parameters instead of concatenating strings, map requests onto explicit DTOs to prevent mass assignment, and return generic error bodies with no stack traces or internal identifiers.
- Limit abuse: per-client rate limits (token bucket) that return 429, maximum page sizes, request body limits, and a gateway or WAF in front. Remember that CORS controls which browsers may read responses; it is not authentication.
- Cover operations: keep secrets in a secret manager rather than in code or config files, write audit logs of who accessed what without logging tokens or sensitive personal data, scan dependencies for known vulnerabilities, and alert on unusual spikes in auth failures.
Follow-up
- A JWT is stolen. How do you revoke it before it expires, and what does that do to your stateless design?
- How would you enforce one rate limit across many instances of the API?
- Walk through exactly how you stop user A reading user B's account by changing the id in the request.
Exports duplicate a row range about once a week
Hard · leases · clock skew · idempotency
Roughly once a week an export writes a file containing a duplicated range of rows. The affected job_run rows show attempt = 1, status = succeeded, one started_at, and a lease_owner naming a different host from the one whose logs show the job starting. Leases last 30 seconds and are heartbeated every 10 from inside the handler; lease_expires_at is computed on the worker and compared against the database's now(). Find the mechanism, and give a fix that holds even if you cannot fix the clocks.
Approach
- Start from the fact that eliminates the obvious answer. attempt = 1 means no retry was recorded, so this is not a re-run after failure; two workers ran the same row concurrently and the takeover path never touched the counter. lease_owner naming a host other than the one that started the job is the same statement from the other side.
- Enumerate the mechanisms that cause a premature takeover, then find the signal that separates them. Either the lease genuinely expired because the heartbeat did not fire, which is what happens when the heartbeat runs on the handler's own thread and the handler makes a long blocking call, or it only appeared expired because two clocks disagree, since lease_expires_at is written from the worker's clock and evaluated against the database's. The discriminator is the distribution: incidents clustered on the longest exports indict the heartbeat, incidents clustered on one host indict skew. Measure both, and measure each host's offset against the database directly.
- Read the reclaim query precisely. In PostgreSQL now() is transaction start time, not statement time, so a reclaimer holding a long transaction compares against an older timestamp than expected; clock_timestamp() is the statement-time function. This is worth ruling in or out before you redesign anything, because it changes which rows look expired.
- Remove the second clock rather than trying to synchronise it. Issue and extend the lease in the database, with lease_expires_at = now() + interval '30 seconds' in both the claim and the heartbeat, so exactly one clock is ever compared and worker skew stops mattering to this predicate.
Follow-up
- The displaced worker has already streamed half the file to object storage. What makes that side effect safe to repeat?
- You now count takeovers. What alert fires on that counter, and at what threshold?
Suggested practice checklist
The plan follows the reported loop: language internals and testing first, then data and design, then behavioral stories, ending with a mixed run-through.
Day 1 — Map the loop and choose your stack
- Write the four reported stages (Initial Screen, Technical Assessments, Team Interactions, Final Review) with the one-line format each is reported to have.
- Declare Java or .NET as your primary language, then list the reported topics: memory and garbage collection, concurrency, indexing, dependency injection, testing, CI/CD, containers and serverless.
- Rate yourself 1 to 5 on each topic and mark the three weakest.
Deliverable: A one-page loop map with your primary language, the rated topic list and the three weakest topics marked.
Day 2 — Memory and concurrency in your language
- Write a short explanation of stack versus heap and garbage collection in your language, including one example of an object that stays reachable longer than intended.
- Write a small shared component (a counter or an in-memory cache) in three versions: with a lock, with a concurrent collection or atomics, and with immutable data.
- Rewrite a routine that loads a large file entirely into memory so that it streams, and note the memory difference.
- Answer the heap and stack, concurrency, and large-dataset memory questions aloud.
Deliverable: A written memory explanation, three working versions of the shared component, the streaming rewrite and spoken answers to three reported questions.
Day 3 — Unit testing and dependency injection
- Build a small service whose collaborators arrive through the constructor, and test it with fakes.
- Write down your testing approach: what you unit test, what you cover with integration tests, and why coverage numbers alone do not show quality.
- Answer the unit-testing and dependency-injection questions aloud, giving one concrete benefit and one cost of dependency injection.
Deliverable: A small tested service plus spoken answers to the unit-test and dependency-injection questions.
Day 4 — Data layer: SQL, NoSQL and concurrent updates
- Write a decision table for choosing SQL or NoSQL for a new microservice, with rows for query shape, transactions, consistency, schema change and scale.
- Practise reading an execution plan and explaining the index you would add and the writes it slows down.
Deliverable: A completed SQL-versus-NoSQL decision table, both practice drills done and a note on one index trade-off.
Day 5 — Design: availability, service calls and API security
- Outline a highly available service for millions of requests per day: load balancing, stateless instances, caching, database replication, health checks and a circuit breaker, and note what breaks first at ten times the load.
- Write when you would use synchronous REST calls between services versus a queue or events, covering timeouts, retries, idempotency and failure handling.
- Write a checklist for exposing a public API: authentication, authorisation, TLS, rate limiting, input validation and secrets handling.
Deliverable: Three design outlines: the highly available service, the service-communication choices and the public API security checklist.
Day 6 — Build the behavioral story bank
- Write four stories from your own work: a slow service, an explained decision, a changed requirement or disagreement, and a production incident or hardest challenge.
- For each story, write situation, your decision, evidence and measurable result, then map it to the six reported behavioral prompts it could answer.
Deliverable: Four written stories mapped to the six prompts, and a recorded or written diagnosis narration for the debugging drill.
Day 7 — Mixed run-through
- Answer aloud in one sitting: a concurrency question, the highly available service design, the architectural-disagreement prompt and the production-issue prompt.
- Review the weakest answer, fix it and say it again.
- Write five questions to ask the team about code review, testing, deployment and the cloud migration and performance work.
Deliverable: Notes from the mixed run with the weakest answer rewritten, plus your five questions for the team.
Stories to prepare for BondR
The reported behavioral prompts cover a cloud performance bottleneck, explaining a decision to a non-technical person, changing approach, architectural disagreement, a production issue and the hardest technical challenge. The role as candidates describe it includes working with product managers and operations, migrating older components and tuning running services, so stories from those situations fit best.
Describe a time you had to troubleshoot a performance bottleneck in a cloud environment.
Medium · System Design and Cloud Architecture
Approach
- Open with the symptom in numbers: which endpoint or job, what latency or throughput, versus what baseline, and who noticed. A story without a before-and-after measurement reads as a guess.
- Narrow the cause layer by layer: load balancer, application tier, database, downstream calls. Name the evidence you used at each layer, such as metrics, traces, logs, slow-query output or garbage-collection pauses, and what each ruled out.
- Describe the specific cause you found (for example a missing index, an exhausted connection pool, a chatty service call, an undersized instance or unbounded memory growth) and why it explained the symptom rather than just correlating with it.
- State the fix and how you verified it: change one thing at a time, re-measure against the baseline, and say what you added afterwards (an alert, a load test, a dashboard) so it would not return silently.
- Mention cost or risk if relevant: why you did not simply add instances, and what the fix cost in complexity or spend.
Follow-up
- How did you separate the real cause from a symptom, and what did you rule out first?
- Why not just scale out, and what would that have cost?
- What did you put in place so the bottleneck would be caught earlier next time?
Tell me about a time you had to explain a complex technical decision to a non-technical stakeholder.
Medium · Behavioral and Problem-Solving
Approach
- Choose a decision you owned that had real consequences for the listener, such as a migration, a delayed feature, a choice of managed service or a security change, and name the person you explained it to (product manager, operations colleague, business owner).
- Start from their concern, not your design: cost, delivery date, customer impact or risk. Translate each technical option into consequences they care about, and present two or three options with a recommendation instead of one conclusion.
- Show how you simplified without distorting: one plain analogy or one small diagram, no jargon, and the one trade-off you refused to hide.
- Close the loop: how you checked that they understood (they restated it, or asked a sharper question), what they decided, and what happened afterwards.
- The mistake that sinks this answer is a story where you explained something and everyone agreed, with no stake, no pushback and no outcome.
Follow-up
- What did they push back on, and how did you respond?
- How did you know they actually understood the trade-off?
- If they had rejected your recommendation, what would you have done?
Describe a situation where you had to pivot your technical approach due to changing project requirements.
Medium · Behavioral and Problem-Solving
Approach
- Set up the original plan in a sentence, then pin down exactly what changed (a requirement, a priority, a constraint discovered late) and when you found out. The timing shows how you detect change.
- Lay out the options you weighed: adapt the current design, rework part of it, or restart. Say how you handled the sunk work, what you kept (interfaces, tests, data model) and what you discarded.
- Describe how you communicated the pivot: what you told the product owner about scope, risk and schedule, and how you got agreement before changing direction.
- Report the outcome with something measurable, such as what shipped, what slipped and what the new design made easier or harder.
- Avoid blaming the requirement change. The better story shows you shortened the cost of change by design, for example through small interfaces or early feedback.
Follow-up
- How could you have spotted the change earlier?
- What did you tell stakeholders about schedule and risk when you changed course?
- What would you design differently now so that a change like that costs less?
How do you handle disagreements with team members regarding architectural choices?
Medium · Behavioral and Problem-Solving
Approach
- Pick a disagreement with genuine technical substance, such as synchronous REST versus an event-driven flow, a relational versus document store, or a monolith split, not a personality clash.
- State the other person's position fairly, in terms they would accept, and then the criteria you agreed to judge by: latency, operational burden, team familiarity, consistency needs and how easily the choice can be reversed.
- Describe how you used evidence instead of opinion: a small prototype, a benchmark, a short written design comparison, or a review with a third engineer.
- Give the result honestly. If your view won, say what you conceded. If it did not, show that you committed to the chosen design and raised concrete signals to watch.
- The mistake to avoid is a win-and-lose narrative in which you were right and they came around; it says nothing about how you collaborate.
Follow-up
- What happened when the other person was more senior than you?
- If you were overruled and the design later had problems, how did you handle it?
- How do you decide when to stop debating and decide?
Give an example of a time you identified and resolved a critical production issue.
Medium · Behavioral and Problem-Solving
Approach
- Lead with impact: what broke, who was affected and how you found out (alert, customer report, a teammate). Then separate the stages cleanly: mitigate, find the cause, fix, prevent.
- Explain your mitigation choice: roll back, disable via a feature flag, fail over or throttle, and why that was safer than fixing forward. Stopping the damage comes before understanding it.
- Walk through root-cause analysis as a sequence: build a timeline, compare what changed (deploy, configuration, traffic, data), read logs and metrics, and reproduce it if you can. Name the actual cause.
- Describe the permanent fix and the follow-ups: a regression test, an alert that would have fired sooner, a runbook entry or a review of how the change got through.
- Be specific about your own role versus the team's, and keep the account blameless; saying who caused it counts against you.
Follow-up
- How did you decide between rolling back and fixing forward?
- How did you communicate with stakeholders while it was ongoing?
- What did you change afterwards to prevent or shorten a repeat?
What is the most difficult technical challenge you have faced, and how did you approach it?
Medium · Behavioral and Problem-Solving
Approach
- Choose a problem whose difficulty was technical, such as a race condition, a migration with live traffic, or a performance cliff, not just a large volume of work or a people problem.
- Define the constraints that made it hard (no downtime, unclear root cause, tight interfaces to other teams, data volume) so the listener can judge the difficulty without your telling them.
- Show your method: what you tried, what failed and what each failure taught you, how you narrowed the search, and whom you consulted.
- State the decision and its trade-off, then the measurable result (error rate, latency, cost, deployment safety).
- Close with what you took from it and a later situation where you applied the lesson; that is the part that shows growth rather than survival.
Follow-up
- Where did you get stuck, and what unblocked you?
- What would you do differently if you faced it again?
- Which part of the solution would you not repeat, and why?