Interview guides · BayOne
BayOne Software Engineer Interview Guide
BayOne Software Engineer interview guide: the 3 reported rounds, Java, DSA and distributed-systems questions with approaches, and a 7-day prep plan.
The 60-second brief
BayOne is described as a technology consulting and talent solutions firm, so its engineers work on client projects rather than on one in-house product. Candidates describe project types such as cloud migrations, cloud-native architectures, distributed systems, high-throughput REST web services, platform systems and AI or machine learning workflows. In this role you design and deploy backend services, work on data pipelines, take part in code reviews and CI/CD pipelines, and turn business requirements into technical specifications together with architects, product owners and client stakeholders.
This guide is for engineers preparing for a BayOne Software Engineer loop. It covers the three reported stages, the three question groups candidates report (Java and backend development, data structures and algorithms, cloud and distributed systems), the scenario-style prompts that appear in the reported evaluation areas, and a seven-day plan. The requirements candidates describe are Java or another major backend language, data structures and algorithms, REST web services and some database experience, with cloud platforms, Docker, Kubernetes and AI/ML frameworks listed as extras.
Candidates report three rounds over about 3-5 weeks: an Initial Recruiter Screen, Technical Evaluations and a Client-Alignment Round. The third stage is reported as depending on the team and role, so confirm with your recruiter whether it applies to you.
Focus areas
- Data Structures
- Algorithms
- Java
Overview
BayOne engineers work on client projects, and the reported work spans backend services, REST APIs, data pipelines, cloud infrastructure and, on some projects, AI or platform systems. That breadth shapes preparation: you are less likely to need one product's domain knowledge than a solid base in Java (or another major backend language), core data structures, database behavior and the design of services that stay up under load.
The questions candidates report fall into three groups. Java and backend development: ArrayList versus LinkedList and their complexities, Java memory management and the garbage collector, the principles of a scalable REST web service, concurrency and thread safety, and optimistic versus pessimistic locking. Data structures and algorithms: level-order traversal of a binary tree, the first non-repeating character in a string, cycle detection in a directed graph, merging two sorted linked lists, and the contiguous subarray with the largest sum. Cloud and distributed systems: consistency across microservices, the CAP theorem and database choice, a rate limiter for a public API, horizontal scaling of a stateless application, and failover and disaster recovery.
The reported evaluation areas also name scenarios worth rehearsing: a thread-safe cache built without the built-in concurrent collections, how a HashMap handles collisions, REST design for large file uploads, merging overlapping intervals, the lowest common ancestor of two tree nodes, turning an O(N^2) algorithm into O(N log N), a notification system that sends millions of pushes, moving a monolith to microservices without downtime, and replication lag in a read-heavy application. The failover question reads as an experience prompt, so prepare it as a story from your own work as well as a design answer.
Candidates report that the technical evaluations follow the stack and architectural patterns of the target project, and that the recruiter says which areas to prioritise. Use the recruiter screen to learn the stack, then weight your time accordingly. The role description lists Redis-style caching, sharding, circuit breakers, retries and rate limiting under distributed systems, and AWS, Azure, GCP, Docker and Kubernetes as nice-to-have experience, so be ready to discuss what you have actually used and to reason from first principles about what you have not.
The BayOne interview process
These are the three stages candidates describe, in the order they report them. Only the first two are reported for every loop; the client-alignment stage is described as depending on the team and role.
Initial Recruiter Screen
Candidates report that the loop opens with an Initial Recruiter Screen about your professional background, your career goals and how they line up with open opportunities. Candidates also report that work arrangements (remote, hybrid or onsite) depend on the client engagement and location and come up here, and that the recruiter indicates which areas to prioritise for the technical stage. Treat it as a two-way call: arrive with a clear account of your Java and backend experience and a short list of questions that will shape the rest of your preparation.
What to demonstrate
- Your professional background and career goals
- How your experience lines up with the open opportunities
- Practical fit such as location and work arrangement
How to prepare
- Write a short spoken summary of your career that ends on your backend, REST and database experience, and name one project where you owned a Java service.
- Map your experience against the listed requirements: Java or another backend language, data structures, REST services, relational or non-relational databases, then cloud platforms, Docker and Kubernetes.
- Ask which client stack the technical evaluation will follow, which areas to prioritise, whether a client-alignment round applies, and what the work arrangement is.
- Have your location constraints and availability ready, and ask for the remaining steps in writing.
Technical Evaluations
Technical Evaluations are reported as covering coding proficiency, data structure knowledge and architectural knowledge. The reported evaluation areas are core Java and object-oriented design (collections, concurrency, RESTful API design), data structures and algorithms (arrays and strings, trees and graphs, dynamic programming and recursion) and cloud and distributed systems (microservices, caching and sharding, resilience patterns). This is where the coding and design questions in this guide are practised. Prepare to write working code in your chosen language, state its complexity, and sketch a service design that names its failure paths.
What to demonstrate
- Working code and the choice of data structure behind it
- Java fundamentals: collections, memory, concurrency and locking
- Architectural knowledge of services, caching, consistency and resilience
How to prepare
- Code level-order traversal, first non-repeating character, directed-graph cycle detection, merging sorted lists and the largest-sum subarray in Java, each with its complexity and edge cases.
- Rehearse the Java topics aloud: ArrayList versus LinkedList, HashMap collisions, heap and garbage collection, thread safety, optimistic versus pessimistic locking.
- Practise the design prompts: a rate limiter, a scalable stateless REST service, a notification system and a monolith-to-microservices migration, each with a failure path.
- Work through the interval-merge, lowest-common-ancestor and O(N^2)-to-O(N log N) scenarios so that you can explain the optimisation step, not only the final code.
Client-Alignment Round
The Client-Alignment Round is reported as a check that your experience fits the project's technical environment, based on the specific team and role requirements; candidates describe it as applying depending on the team and role. The role itself involves working with architects, product owners and client stakeholders, so expect the conversation to sit closer to your project experience and communication than to a pure algorithm session. Prepare by learning the project's stack from your recruiter and linking your past work to it.
What to demonstrate
- Fit between your experience and the project's technical environment
- How you explain technical choices to architects, product owners and client stakeholders
- Your approach to turning requirements into technical specifications
How to prepare
- Take the stack details from the recruiter screen and write down where your experience matches and where you would be learning, with a plan for each gap.
- Prepare two or three project stories covering a service you built, a trade-off you explained to a non-technical person and a requirement you clarified before building.
- Rehearse the failover and disaster-recovery story so it covers both the design and your personal part in a real event.
- Prepare questions about the project: the stack, how releases reach production, and how the team handles code review and CI/CD.
Where candidates lose points
Calling a LinkedList faster for insertion without counting the traversal to the position, or ignoring ArrayList's amortised O(1) append and cache locality
State each operation separately: get by index is O(1) on ArrayList and O(n) on LinkedList, insertion in the middle needs a shift or a walk, and LinkedList insertion is O(1) only when you already hold the node or are at an end. Say which workload you would choose each for.
Writing the thread-safe cache with one coarse synchronized block, or with a plain HashMap and a check-then-act race, when asked to avoid the built-in concurrent collections
Name the shared state and the invariant, then pick the lock deliberately: a ReentrantReadWriteLock or lock striping, with the lookup-and-insert done as one atomic step. Run it under several threads and say what the lock costs under contention.
Coding the tree, list, string or graph questions before checking edge cases: empty tree, all-negative array, no unique character, or an undirected visited set in cycle detection
Clarify input size and degenerate cases first, then test them by hand after writing. Directed cycle detection needs three states (unvisited, on the current path, done) or a topological sort, not a single visited flag.
Answering the CAP, microservice consistency or replication-lag prompts with slogans such as 'pick two' or 'eventually consistent' without naming what the user sees
Say what the system does during a partition, operation by operation, and describe the visible anomaly you accept, such as a stale read after a write. Then name the mechanism that reconciles it: a saga with compensating steps, an outbox, or idempotent retries.
Presenting the rate limiter or failover design as a list of components (Redis, Kubernetes, load balancer) with no algorithm and no failure path
For the rate limiter, pick an algorithm (token bucket, sliding window), say where counters live, how they are shared across instances, what the client sees when it is limited, and what happens if the counter store is down. For failover, state recovery targets before naming tools.
Questions & practice
Explain the difference between an ArrayList and a LinkedList, and describe the time complexity of their primar
Medium · Java & Backend Development
Explain the difference between an ArrayList and a LinkedList, and describe the time complexity of their primary operations.
Approach
- ArrayList is backed by a resizable Object[]: get(i) and set(i) are O(1), add at the end is amortized O(1) because a full array grows by about 1.5x and copies, and add or remove at an index is O(n) because System.arraycopy shifts the tail.
- LinkedList is a doubly linked list that implements both List and Deque: addFirst, addLast, removeFirst and removeLast are O(1), but get(i) is O(n) because it walks from whichever end is nearer.
- Insertion in the middle of a LinkedList is O(1) only once you hold the position through a ListIterator; finding that position is still O(n). contains and indexOf are O(n) in both.
- Memory and locality: each LinkedList node carries an item, next and prev reference plus an object header, and nodes are scattered on the heap, so iteration is slower than over a contiguous array. ArrayList can hold unused capacity (trimToSize releases it).
- Conclusion I would state: ArrayList is the default for indexed access and iteration, and for a queue or stack I would reach for ArrayDeque rather than LinkedList.
Follow-up
- Why is ArrayList often faster than LinkedList even for inserts in the middle of a list of moderate size?
- What happens if you remove elements inside a for-each loop over an ArrayList, and how do Iterator.remove or removeIf avoid ConcurrentModificationException?
- When would you pick ArrayDeque over LinkedList, and what does ArrayDeque refuse to store?
How does memory management work in Java, and what is the role of the Garbage Collector?
Medium · Java & Backend Development
Approach
- Lay out the memory areas: each thread has a stack of frames holding local primitives and references; objects live on the shared heap; class metadata lives in Metaspace since Java 8 (it replaced PermGen).
- The garbage collector frees objects that are unreachable from GC roots (thread-stack locals, static fields, JNI references). Reachability tracing means reference cycles are collected, unlike reference counting.
- Generational collection: new objects go to Eden, minor GCs copy survivors between the survivor spaces, and long-lived objects are promoted to the old generation. Name the collectors: Serial, Parallel, G1 (the default since JDK 9, region-based with a pause-time goal), and ZGC or Shenandoah for low pause times.
- Java still leaks memory when references stay reachable: static collections, caches with no eviction, listeners never unregistered, ThreadLocal values in pooled threads. I diagnose with -XX:+HeapDumpOnOutOfMemoryError, a heap analyzer such as Eclipse MAT, and GC logs (-Xlog:gc*).
- The GC only manages memory: files, sockets and connections need try-with-resources, and finalize() is deprecated, so nothing should rely on it.
Follow-up
- How would you tell apart and fix 'OutOfMemoryError: Java heap space' and 'OutOfMemoryError: Metaspace'?
- What is a stop-the-world pause, and how do minor, mixed and full collections differ in G1?
- What do WeakReference and WeakHashMap give you, and when is a weak-keyed cache the wrong tool?
How do you handle concurrency and thread safety in a multi-threaded Java application?
Medium · Java & Backend Development
Approach
- Start by finding the shared mutable state; the cheapest fixes are to remove it: thread confinement, immutable objects with final fields, and safe publication.
- Explain the Java Memory Model guarantees: synchronized gives mutual exclusion plus visibility through happens-before; volatile gives visibility and ordering but not atomicity, so count++ on a volatile field is still a race.
- Use java.util.concurrent rather than hand-rolled locking: AtomicInteger or LongAdder for counters, ConcurrentHashMap with computeIfAbsent or merge for per-key atomic updates, ReentrantLock when you need tryLock with a timeout or Conditions, ReadWriteLock for read-heavy data, and ExecutorService instead of raw threads.
- Name the classic bugs: check-then-act (containsKey then put) and read-modify-write races, fixed with atomic map operations or a single lock around the compound action.
- Deadlock needs mutual exclusion, hold-and-wait, no preemption and circular wait; prevent it with a global lock ordering, tryLock timeouts and short critical sections, and diagnose it from a jstack thread dump.
Follow-up
- Why is double-checked locking for a lazy singleton broken without volatile?
- When would you choose ReentrantLock over synchronized?
- How would you size a thread pool for CPU-bound work versus work that mostly waits on I/O?
Given a binary tree, write an algorithm to perform a level-order traversal.
Medium · Data Structures & Algorithms (DSA)
Approach
- Use BFS with a queue (ArrayDeque in Java): enqueue the root if it is non-null, then loop while the queue is non-empty.
- Snapshot size = queue.size() at the start of each level, poll exactly that many nodes into the level's list, and enqueue each node's left then right child; this groups the output into a list per level without sentinel markers.
- Complexity: O(n) time since each node is enqueued and dequeued once; O(w) extra space where w is the maximum width, up to about n/2 on the last level of a complete tree.
- Edge cases: a null root returns an empty list; a skewed tree has width 1; ArrayDeque throws on null, so check children before offering them.
- Mention the DFS alternative that passes depth and appends to result[depth], using O(h) stack, and the common variants: zigzag order, right-side view (last node per level), bottom-up order and per-level averages.
Follow-up
- Return the levels in zigzag order. What changes?
- How would you find the minimum depth of the tree, and why can BFS stop early there?
- How would you do this without the size snapshot, for example with two queues or a null marker?
Implement a function to find the first non-repeating character in a given string.
Medium · Data Structures & Algorithms (DSA)
Approach
- Two passes: count every character's frequency, then scan the string again in order and return the first character whose count is 1. O(n) time and O(k) space for an alphabet of size k.
- Pick the counter from the clarified input: int[26] for lowercase letters, int[128] or int[256] for ASCII, a HashMap<Character, Integer> for arbitrary text.
- Clarify the contract first: return the character or its index, what to return when none exists (-1 or a sentinel), the empty string, and whether case and whitespace count.
- Unicode: a Java char is a UTF-16 code unit, so emoji and other supplementary characters span two chars. Iterate s.codePoints() and count by code point if the input can contain them.
- Contrast with the O(n^2) nested-loop brute force, and mention a LinkedHashMap alternative whose second pass walks distinct characters in insertion order instead of the whole string.
Follow-up
- The characters arrive as a stream. How do you report the first non-repeating character after each one (queue of candidates plus counts, amortized O(1))?
- How does your solution change for 'first unique integer' in a large array?
- What breaks if the input contains emoji and you iterate with charAt?
How would you detect a cycle in a directed graph?
Medium · Data Structures & Algorithms (DSA)
Approach
- DFS with three states per node: unvisited, on the current recursion stack, finished. An edge to a node that is on the stack is a back edge, which means a cycle.
- Explain why a single visited set is wrong for directed graphs: reaching an already-finished node through a cross edge is not a cycle, so you need the separate on-stack state.
- Alternative: Kahn's algorithm. Compute in-degrees, queue every zero in-degree node, pop and decrement neighbours; if fewer than V nodes are processed, the rest are in or behind a cycle. It also produces a topological order.
- Both run in O(V + E) time and O(V) space with adjacency lists. Start DFS from every unvisited node to cover disconnected parts, and treat a self-loop as a cycle.
- Recursion can overflow the Java stack on long chains, so use an iterative DFS or Kahn's for large graphs. To print the cycle, keep parent pointers and walk back from u to v when the back edge u->v is found.
Follow-up
- Given course prerequisites, return a valid order or report that none exists.
- How does cycle detection differ in an undirected graph (parent check or union-find)?
- How would you return the nodes that form the cycle, not just a boolean?
Write a program to merge two sorted linked lists into a single sorted list.
Medium · Data Structures & Algorithms (DSA)
Approach
- Use a dummy (sentinel) head and a tail pointer. While both lists are non-null, attach the smaller node and advance that list; using <= takes equal values from the first list first, which keeps the merge stable.
- When one list runs out, attach the remainder of the other in a single step rather than looping over it.
- Complexity: O(n + m) time and O(1) extra space, because existing nodes are relinked rather than copied.
- The recursive version is shorter but uses O(n + m) stack depth, which can overflow on long lists, so I would write the iterative one.
- Edge cases: one or both lists null, very different lengths, duplicate values, and every element of one list being smaller than the other's head.
Follow-up
- Merge k sorted lists. Compare a min-heap (O(N log k)) with pairwise divide-and-conquer merging.
- Sort a singly linked list in O(n log n) time using this merge as the building block.
- How would you merge two sorted arrays in place when the first has spare capacity at the end?
Given an array of integers, find the contiguous subarray with the largest sum.
Medium · Data Structures & Algorithms (DSA)
Approach
- Start from the brute force: O(n^2) by fixing a start and extending with a running sum.
- Kadane's algorithm: cur = max(x, cur + x) and best = max(best, cur) for each element. Initialise both with the first element so an all-negative array returns its largest element. O(n) time, O(1) space.
- Clarify whether the subarray must be non-empty; if an empty subarray is allowed, the answer is max(0, Kadane's result).
- To return the subarray itself, record a tentative start whenever cur resets to x, and save start and end whenever best improves. Use long for sums if values are large enough to overflow int.
- Mention equivalent views: with prefix sums the answer is the maximum of prefix[j] - min(prefix[i]) for i < j, and a divide-and-conquer solution runs in O(n log n).
Follow-up
- Solve it for a circular array (max of Kadane and total minus the minimum subarray, with the all-negative case handled).
- Find the maximum product subarray instead. Why must you track both the running max and the running min?
- Extend it to the maximum-sum submatrix of a 2D grid.
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
- 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.
- 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.
- 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.
- 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?
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?
What are the core principles of designing a scalable REST web service?
Medium · Java & Backend Development
Approach
- Model resources as nouns and use HTTP semantics correctly: GET is safe and idempotent, PUT and DELETE are idempotent, POST is not. Return precise status codes (201 with Location, 204, 400, 404, 409, 429, 503).
- Keep the service stateless: no in-memory session, authentication carried on every request as a token, so any instance behind the load balancer can serve any request and you scale by adding instances.
- Cut read load with caching: Cache-Control and ETag with If-None-Match returning 304, plus a server-side cache such as Redis for hot reads, with an explicit invalidation rule on writes.
- Design for large data and retries: cursor (keyset) pagination instead of deep OFFSET, filtering parameters, an Idempotency-Key header on POSTs that create things, and a versioning scheme (/v1/ path or header).
- Protect the data tier and slow paths: connection pooling, indexes and read replicas; long-running work goes to a queue and returns 202 Accepted with a status resource; add timeouts, rate limits and request IDs for tracing.
Follow-up
- How would you make an order-creating POST safe for a client to retry after a timeout?
- What is the trade-off between offset pagination and cursor pagination on a table with millions of rows?
- How do you introduce a breaking change to a response shape without breaking existing clients?
Explain the difference between optimistic and pessimistic locking in database transactions.
Medium · Java & Backend Development
Approach
- Pessimistic locking takes the lock before the read: SELECT ... FOR UPDATE holds a row lock until commit and other writers block. It suits high contention or conflicts that are expensive to redo, at the cost of throughput and deadlock risk.
- Optimistic locking takes no lock: add a version column and run UPDATE ... SET ..., version = version + 1 WHERE id = ? AND version = ?. Zero rows updated means someone else changed the row, so retry or report a conflict. JPA's @Version does this and throws OptimisticLockException.
- Trade-off: optimistic scales well when conflicts are rare and works across user think-time, because no lock is held between HTTP requests; under heavy contention the retries waste work and can starve a writer.
- Mitigations for pessimistic locks: lock rows in a consistent order, set lock timeouts or use NOWAIT, and for job queues use FOR UPDATE SKIP LOCKED so workers do not wait on each other.
- Tie both to the lost-update anomaly under READ COMMITTED, and note that PostgreSQL SERIALIZABLE (serializable snapshot isolation) aborts conflicting transactions with a serialization failure, which the application must retry.
Follow-up
- For decrementing stock during a flash sale, which approach would you use, and does a single conditional UPDATE ... WHERE stock > 0 avoid both?
- Walk through how two transactions deadlock with row locks and what the database does when it detects it.
- How would you surface an optimistic-lock conflict to a user editing a form?
How do you ensure data consistency across multiple microservices in a distributed environment?
Medium · Cloud & Distributed Systems
Approach
- State the constraint: with a database per service there is no single ACID transaction. Two-phase commit exists but blocks on a coordinator failure and many brokers do not support it, so it is usually avoided.
- Saga pattern: a chain of local transactions, each with a compensating action (issue a refund, release a reservation) that runs if a later step fails. Compare orchestration (a coordinator tracks saga state) with choreography (services react to each other's events).
- Avoid the dual-write problem with a transactional outbox: write the business row and the event row in the same local transaction, then a relay (a poller or change data capture such as Debezium) publishes the event to the broker.
- Delivery is at-least-once, so consumers must be idempotent: store processed event IDs, or make updates conditional. Partition events by entity ID to keep per-entity ordering.
- Be explicit about eventual consistency: name the anomaly a user could see, read from the owning service where read-your-writes matters, and run reconciliation jobs as a backstop.
Follow-up
- What happens if a compensating action itself fails?
- How do you keep events for the same order in sequence across consumers?
- When would you choose orchestration over choreography?
Explain the CAP theorem and how it influences your choice of database systems.
Medium · Cloud & Distributed Systems
Approach
- State it precisely: when a network partition happens, a replicated store must choose between consistency (linearizability: every read sees the latest acknowledged write) and availability (every request to a non-failing node gets a non-error response). Partitions cannot be ruled out, so the real choice is CP or AP during a partition.
- CP examples: consensus-based stores such as ZooKeeper and etcd refuse writes on the minority side. AP-leaning examples: Cassandra keeps accepting reads and writes and reconciles later, with tunable per-request consistency levels.
- Explain quorums: with N replicas, write quorum W and read quorum R, choosing R + W > N makes read and write sets overlap so a read sees the latest acknowledged write (Cassandra QUORUM for both).
- Add PACELC: even without a partition you trade latency against consistency, which drives most everyday database choices more than CAP does.
- Map to requirements: balances, inventory and uniqueness constraints favour consistency; carts, feeds and counters can favour availability with conflict resolution such as last-write-wins or CRDTs.
Follow-up
- How does the C in CAP differ from the C in ACID?
- Your CP store rejects writes during a partition. What does your application do for its users?
- Where does a single PostgreSQL primary with asynchronous read replicas sit, and what anomaly can replica reads show?
Describe how you would design a rate limiter for a public-facing API.
Medium · Cloud & Distributed Systems
Approach
- Pin down requirements: what the key is (API key, user, IP), the limits (per second and per day, per plan), and the response: 429 Too Many Requests with Retry-After, plus remaining-quota headers such as X-RateLimit-Remaining.
- Compare algorithms: fixed window counter (simple, but allows up to twice the limit across a window boundary), sliding window log (exact, memory per request), sliding window counter (weighted estimate from the previous window), token bucket (capacity sets the burst, refill rate sets the sustained rate) and leaky bucket (smooths output).
- Distributed state: keep counters in Redis so every gateway instance shares them. INCR is atomic; set EXPIRE when the key is first created, or run the whole check in a Lua script. For a token bucket, store tokens and the last refill time and refill lazily on each request.
- Placement and failure modes: enforce at the API gateway or middleware before expensive work, decide fail-open or fail-closed if Redis is down, use Redis server time to avoid clock skew, and add a local in-memory limiter for very hot keys.
- Complexity: O(1) per request for counters and token buckets; the sliding log costs O(requests in window) memory per key.
Follow-up
- Two app instances read a count, both see 99 of 100 and both allow the request. How do you prevent that?
- How would you rate-limit across multiple regions without a cross-region call on every request?
- Many legitimate users share one IP behind a NAT. How does that change your key choice?
What strategies do you use to scale a stateless web application horizontally?
Medium · Cloud & Distributed Systems
Approach
- Make instances interchangeable: move sessions into tokens or a shared store such as Redis, uploads into object storage, and configuration into the environment, so nothing a request needs lives on one machine.
- Put the instances behind a load balancer (round robin or least connections) with health checks that separate readiness from liveness, and drain connections on deploy. Sticky sessions become unnecessary.
- Autoscale on a signal that tracks load: CPU, request rate, latency or queue depth. Build immutable container images, keep a minimum instance count for warm capacity, and handle SIGTERM for graceful shutdown during rolling or blue-green deploys.
- The bottleneck moves to shared state: per-instance connection pools times instance count can exceed the database's max_connections, so add a pooler such as PgBouncer, read replicas and caching. Serve static assets from a CDN and push heavy work onto a queue.
Follow-up
- Traffic grows tenfold. What breaks first, and how would you find out before it does?
- How do you scale an app that holds long-lived WebSocket connections?
- When are sticky sessions acceptable, and what do they cost during a deploy or an instance failure?
One log partition stops advancing while the others drain
Easy · poison message · head-of-line blocking · consumer lag
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
Suggested practice checklist
The plan follows the three reported question groups: Java fundamentals first, then data structures and algorithms, then the design questions and your failover story. Day 1 covers the recruiter screen and the project-stack conversation.
Day 1 — Recruiter screen and stack map
- Write a short spoken summary of your career that leads to your Java, REST and database experience, and say it aloud twice.
- Build a table of the listed requirements (Java or another backend language, data structures, REST services, databases, then cloud platforms, Docker, Kubernetes, AI/ML frameworks) against what you have used, with one project per row.
- Write the questions you will ask the recruiter: the client stack, the areas to prioritise, whether a client-alignment round applies, and the work arrangement.
Deliverable: A career summary, a requirements-versus-experience table and a list of recruiter questions.
Day 2 — Java collections and memory
- Write a table of ArrayList and LinkedList operations (get, append, insert at head, insert in the middle, remove) with their complexities and one sentence on when you would pick each.
- Explain aloud how a HashMap handles collisions, including buckets, resizing and the conversion of long bins to trees.
- Explain Java memory: stack versus heap, reachability, generations, what a garbage collection pause is, and one way to find a memory leak.
Deliverable: A one-page Java collections and memory sheet, plus a spoken answer to each of the two reported Java questions.
Day 3 — Concurrency and locking
- Write a thread-safe cache in Java without ConcurrentHashMap, using a ReentrantReadWriteLock or lock striping, and run it from an ExecutorService with many threads.
- List the other tools you would name for thread safety: immutability, synchronized, volatile, atomics and thread confinement, each with one use.
- Write the SQL for optimistic locking with a version column and the SELECT FOR UPDATE form for pessimistic locking, and state when each wins.
Deliverable: A tested cache class, a thread-safety checklist and a two-line rule for choosing optimistic or pessimistic locking.
Day 4 — Trees, lists and strings
- Code level-order traversal with a queue, returning a list of levels, and handle the empty tree.
- Code the first non-repeating character with a count map and a second pass, then discuss a single-pass variant using an ordered map.
- Code the merge of two sorted linked lists with a dummy head, then solve the interval-merge and lowest-common-ancestor scenarios.
Deliverable: Five tested Java solutions, each annotated with time and space complexity and three edge-case tests.
Day 5 — Graphs and arrays
- Code directed-graph cycle detection twice: a DFS with three states and Kahn's topological sort, and note when each is preferable.
- Code the largest-sum contiguous subarray with Kadane's algorithm, including an all-negative array, then return the indices of the subarray.
- Take an O(N^2) pairwise solution and rewrite it with sorting, a hash map or a heap, and state what you traded for the speedup.
Deliverable: Two graph solutions, one array solution with indices, and a written before-and-after optimisation with complexities.
Day 6 — REST and distributed design
- Sketch a scalable REST service: resource naming, status codes, pagination, idempotent writes, statelessness, caching headers and a large-file upload flow.
- Design a rate limiter for a public API: pick token bucket or sliding window, put the counters in Redis, and say what happens when Redis is unavailable.
- Write the consistency answer for microservices (saga, outbox, idempotency) and the CAP answer, then outline how you scale a stateless service horizontally.
Deliverable: Three one-page design sketches (REST service, rate limiter, consistency and scaling), each with a failure path.
Day 7 — Failover story and full rehearsal
- Write your failover and disaster-recovery story: the system, its recovery targets, the event or drill, your decision, the metrics and the lesson.
- Run a mock technical evaluation: one coding question, one Java question and one design question back to back, then a conversation about how your experience fits a given project stack.
Deliverable: A written failover story and a recorded or noted mock run listing the weakest answer to revisit.
Project stories and the failover question
Only one reported question is behavioral in form: how you handle failover and disaster recovery. The recruiter screen and the client-alignment round are also described as conversations about your background, goals and fit with a project, and the role involves working with architects, product owners and client stakeholders. Prepare stories where you made the decision, with numbers where you have them.
- Tell me about a time you explained a technical trade-off to a non-technical stakeholder or client.
- Describe a project where the requirements were unclear and you had to ask questions before building.
- Tell me about a time you had to learn an unfamiliar framework, cloud service or language quickly to deliver a project.
- Describe a disagreement with an architect or product owner about a design, and how you settled it.
- Walk me through a production bug you traced to concurrency or data consistency.
How do you handle failover and disaster recovery in a cloud-hosted environment?
Medium · Cloud & Distributed Systems
Approach
- Treat it as an experience prompt first. Name one real system you ran (or one failover drill you led), say what it served, and state its recovery targets: how long it could be down (RTO) and how much recent data it could lose (RPO). If you cannot name them, say what you inferred from the business impact.
- Walk the layers in order. For the stateless tier: instances spread across availability zones behind a load balancer with health checks. For the database: a replica, a promotion path and point-in-time backups. For a regional loss: cold, pilot-light, warm-standby or active-active, and what each costs against how fast it recovers.
- Name the hazards you handled. Asynchronous replication can lose the latest writes on promotion; the old primary must be fenced so two nodes never accept writes; clients need idempotent retries and timeouts; a cold cache after failover can flood the database.
- Give the proof. Describe the restore test, game day or failover exercise you ran, the timings you measured (time to detect, time to promote, error rate during the switch) and the runbook or alert you changed afterwards. An untested backup is not a recovery plan.
- Close with your own contribution and a lesson: the decision you made, the result in numbers, and what you would change. The mistake that sinks this answer is reciting a list of cloud services with no event, no metric and no personal decision attached.
Follow-up
- How would you choose between warm standby and active-active for this system, and what does the extra cost buy?
- What happens to writes the old primary accepted just before the failover, and how do you reconcile them?
- How do you know your backups restore, and how often have you actually tested one?
Narrate an outage you owned from page to postmortem
Hard · incident response · blast radius · postmortems
Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.
Approach
- Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
- Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
- Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
- Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.
Follow-up
- What would you do differently in the first five minutes, given the same dashboard and no more information?
- Which follow-up action did you deliberately not take, and why was dropping it the right call?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?