Interview guides · Ayvens
Ayvens Software Engineer Interview Guide
Ayvens Software Engineer interview guide: recruiter screen, technical evaluations, SOLID, SQL tuning, load balancing and a 7-day prep plan.
The 60-second brief
Ayvens works in corporate vehicle leasing and fleet management, which candidates describe as sustainable mobility. Candidates describe the Software Engineer role as backend-centred: building services and microservices, tuning database queries, connecting enterprise systems, and refactoring older workflows, with cloud infrastructure in the picture. The role is described as working alongside product managers, system architects and QA engineers, and as reaching into deployment, monitoring and production troubleshooting.
This guide is for Software Engineer candidates in the Ayvens loop. It covers the two reported stages, the ten reported questions (architecture fundamentals, design principles and code quality, practical problem-solving), and a seven-day plan. No programming language is reported for the role, so pick the object-oriented language you know best and use it for every example.
Candidates report two rounds over roughly 2-4 weeks: a Recruiter Screen, then Technical Evaluations. Rounds can differ by team, seniority and location.
Focus areas
- SOLID Principles
- Load Balancing
- System Architecture
Overview
The requirements candidates describe for this role are concrete: an object-oriented language, SQL and database optimization, system architecture including load balancers, and working knowledge of SOLID. Nice-to-have items are cloud infrastructure, legacy refactoring, high-throughput transaction systems and CI/CD. The reported questions line up with that list, so preparation is about fundamentals you can explain with trade-offs, not about obscure puzzles.
The ten reported questions fall into three groups. Architecture fundamentals: how load balancers spread traffic and keep sessions, what makes a distributed system highly available, how object-oriented principles hold up in large enterprise code, and how to diagnose a slow production query. Design principles and code quality: SOLID with a Single Responsibility example from your own work, refactoring tightly coupled legacy modules, and running code reviews without stalling delivery. Practical problem-solving: analysing a snippet for security flaws or bottlenecks, reducing time or space complexity, and isolating intermittent concurrency bugs.
Several of these are phrased as 'how do you' questions, so the strongest answers are procedures with a named first step, a named tool or check, and a stated trade-off. Two of them ask about your own experience (the SRP example and the code review question), which means you need real stories ready, with the situation kept short and the reasoning given most of the space.
Candidates also report scenario prompts: securing an endpoint exposed to injection or unauthorised data access, designing a service that absorbs sudden traffic spikes, and structuring storage for a high-throughput transaction system. Practise reading short snippets aloud and naming both the flaw and the fix, since code analysis is described as part of the technical stage.
The Ayvens interview process
Candidates report two stages in this order. The technical stage is described only by what it assesses, so prepare by question type instead of assuming a fixed schedule.
Recruiter Screen
Candidates describe the Recruiter Screen as a first conversation that lines up your experience, location and expectations for the role. It is a conversation about fit and logistics, so the work is preparing a tight account of your background against the requirements candidates describe: backend services, SQL, object-oriented design, production ownership.
What to demonstrate
- How your experience lines up with the role
- Location and working-arrangement fit
- Shared understanding of what the role involves
How to prepare
- Write a short CV walkthrough that puts backend services, database work and production troubleshooting first, with one concrete result for each.
- List your location and availability constraints so you can state them plainly.
- Prepare questions about what the technical stage covers and which language and stack the team uses, because no language is reported.
- Pick one project and be ready to name its data store, how it was deployed and what you owned.
Technical Evaluations
Candidates describe Technical Evaluations as assessments of coding analysis, architectural reasoning and domain knowledge. Reported preparation advice includes reviewing unfamiliar code for security flaws and performance bottlenecks, talking through design trade-offs, and knowing SOLID and SQL tuning. Which specific questions appear in this stage is not reported, so cover all three question groups.
What to demonstrate
- Coding analysis: reading code and reasoning about its performance and security
- Architectural reasoning about scalability, availability and data storage
- Domain knowledge of object-oriented design and databases
How to prepare
- Rehearse the slow-query procedure end to end: capture the statement, read the execution plan, change an index or the query, then re-measure.
- Review three short snippets (string-built SQL, a loop that queries per row, a nested-loop duplicate check) and state each flaw, the fix and the complexity change.
- Sketch a load-balanced, highly available service with session handling, and say what happens when each box fails.
- Prepare a before-and-after example for the Single Responsibility Principle and a plan for untangling a tightly coupled module.
Where candidates lose points
Reciting the five SOLID names but giving no concrete Single Responsibility example from real code
Prepare one class you split: what it did before, why that was a problem (a change in one concern breaking another), what the split looked like, and how tests got easier. Have a second story where breaking a principle made a later change costly.
Treating a slow query as an indexing problem and adding an index without reading the plan
State the order: capture the statement, run the plan, check row estimates and selectivity, then look for a function on an indexed column, a missing composite index, a per-row query loop or stale statistics. Re-measure after the change and mention the write cost of every new index.
Reviewing a code snippet only for speed and missing the security flaw, or the reverse
Use a two-pass read: first untrusted input (string-built SQL, missing authorisation on returned data, secrets in code), then cost (queries in loops, nested scans, unbounded result sets). Name the fix with the complexity change.
Presenting an availability design with a single balancer, a single database or sticky sessions and no failure story
Walk each component and say what happens when it dies: redundant balancers, health checks, stateless services with a shared session store, a replicated database, and where you accept stale reads.
Debugging a concurrency issue by guesswork, such as wrapping everything in locks
Give a sequence: capture thread dumps and thread-tagged logs, identify shared mutable state, classify the bug as a race, deadlock or visibility problem, reproduce it with a stress test, fix the narrowest critical section, and keep the stress test as a regression check.
Questions & practice
What steps do you take to optimize an algorithm that exhibits high time or space complexity?
Medium · Practical Problem-Solving and Code Analy
Approach
- Measure first: state the current complexity in terms of the real input sizes, find the dominant term, and confirm with a profiler that the hot spot is where the analysis says it is.
- Remove repeated work: a hash set or map turns pairwise O(n^2) lookups into O(n) expected; prefix sums answer range sums in O(1) after O(n) setup; sorting plus two pointers or binary search gives O(n log n); memoization or dynamic programming turns exponential recursion with overlapping subproblems, such as naive Fibonacci, into O(n).
- Choose the data structure from the access pattern: a heap for repeated min or max (top-k in O(n log k)), a deque for sliding-window maximum in O(n), a balanced tree or sorted container for ordered queries, union-find for connectivity.
- For space: a 2D DP that only reads the previous row can keep two rows, cutting O(n*m) memory to O(min(n, m)); stream or chunk input instead of loading it all; work in place where mutation is allowed. Say explicitly when you are trading memory for time.
- Keep the brute force as a test oracle, run randomized comparisons between the two versions, benchmark at realistic sizes, and weigh constant factors and readability against the asymptotic gain.
Follow-up
- When can an O(n log n) solution be slower than an O(n^2) one in practice?
- The input no longer fits in memory. What changes in your approach?
- How would you cut the memory of a two-dimensional dynamic programming table?
Track a rolling failure rate per destination for circuit decisions
Easy · sliding window · ring buffer · circuit breaker
The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.
Approach
- Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
- Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
- State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
- Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.
Follow-up
- The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
- A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Replace offset paging on the resource feed with keyset
Easy · keyset pagination · composite index · sort order
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
Approach
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
Follow-up
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
What are the key architectural considerations when designing a high-availability distributed system?
Medium · Technical and Architecture Fundamentals
Approach
- Start from the failure model and the target: which failures must be survived (instance, zone, region, a dependency) and what availability is needed. Turn it into numbers: 99.9% allows about 8.76 hours of downtime a year, and two serial dependencies at 99.9% each give roughly 99.8% combined.
- Remove single points of failure: stateless app instances behind a load balancer with health checks, spread across at least two zones; state in a replicated store; leader election through a consensus system (Raft, etcd, ZooKeeper) sized at 3 or 5 nodes so a majority survives 1 or 2 failures.
- State the replication trade-off: synchronous replication gives zero data loss on failover at the cost of write latency; asynchronous is faster but can lose recent writes. During a partition you choose consistency or availability; quorum reads and writes with R + W > N make reads overlap the latest write.
- Contain failures: timeouts on every remote call, retries with exponential backoff and jitter, idempotency keys so retries are safe, circuit breakers, bulkheads, load shedding, and degraded modes such as serving cached data.
- Make it operable: separate liveness and readiness checks, rolling or blue-green deploys, failover that is exercised regularly, alerts on latency, traffic, errors and saturation, and backups whose restore has actually been tested.
Follow-up
- The network splits between two zones and both think they hold the primary. How do you prevent split-brain (quorum, fencing tokens)?
- How would you run a schema migration on this system without downtime?
- Which part of your design would you sacrifice first if the budget allowed only one zone?
Can you explain how object-oriented programming principles apply to large-scale enterprise applications?
Medium · Technical and Architecture Fundamentals
Approach
- Encapsulation: keep invariants inside the object. A lease contract changes state only through methods that validate the transition (for example, no termination before activation), instead of public setters any caller can misuse; the internal representation can then change without breaking callers.
- Abstraction and polymorphism: depend on interfaces such as a payment gateway or notification sender so implementations swap without touching callers, and replace growing type switches with dispatch. This is what makes adding a new case an addition rather than an edit.
- Composition over inheritance: deep hierarchies suffer from the fragile base class problem, where a change in the parent breaks subclasses. Use inheritance only for true is-a relationships where a subclass can stand in for its parent; otherwise compose small collaborators.
- Structure at scale: a layered or ports-and-adapters layout keeps the domain model free of framework and database code, modules expose a small public API, and dependency injection wires concrete implementations at the edge, which also lets tests pass in fakes.
- Name the costs: interfaces with a single implementation, anemic domain objects that are only data plus service classes holding all logic, and friction between ORM entities and domain objects. Say when you accept each.
Follow-up
- When would you pick inheritance over composition, and what would make you regret it?
- What is the difference between an anemic and a rich domain model, and when is anemic acceptable?
- How does dependency injection make a class with a database dependency unit-testable?
What are the SOLID principles, and can you provide an example of how you applied the Single Responsibility Pri
Medium · Software Design Principles and Code Qual
What are the SOLID principles, and can you provide an example of how you applied the Single Responsibility Principle in a recent project?
Approach
- Define all five precisely. Single responsibility: a module has one reason to change. Open/closed: extend behaviour by adding code, not editing working code. Liskov substitution: a subtype must work wherever its base type is expected (the square-extends-rectangle setter is the standard violation). Interface segregation: clients should not depend on methods they do not use. Dependency inversion: high-level policy and low-level details both depend on abstractions.
- Give a concrete before and after for SRP, in the first person: for example, an invoice service that calculated totals, rendered a PDF and sent the email. I split it into a calculator, a renderer and a sender behind small interfaces.
- Show the evidence it helped: a tax-rule change no longer touched rendering code, calculation tests stopped needing a mail server mock, diffs got smaller and merge conflicts in that file dropped. Use the reason-to-change test to justify where you drew the line.
- Show judgement: SRP does not mean one method per class, and splitting too far scatters one piece of logic across many files. Apply it where two different groups of people ask for changes to the same class.
Follow-up
- Describe a Liskov substitution violation you have seen and how you fixed it.
- How is dependency inversion different from dependency injection?
- Tell me about a time applying one of these principles made the code worse.
How do you approach code refactoring when dealing with tightly coupled legacy modules?
Medium · Software Design Principles and Code Qual
Approach
- Build a safety net first: characterization tests that pin current behaviour, quirks included, before changing anything. For output-heavy code, golden-master or approval tests that compare whole outputs are the fastest way in.
- Find seams so the code can be tested in isolation: extract an interface around the hard dependency (database, static call, system clock, network), pass it through the constructor, and use sprout method or sprout class to write new logic outside the tangled code.
- Move incrementally: put a facade in front of the module and migrate pieces behind it one at a time (strangler fig), keep commits small and reversible, use feature flags, and run old and new paths in parallel comparing outputs where correctness matters.
- Prioritise with data: git history shows high-churn files and files that always change together; combine that with complexity and dependency cycles to pick the refactor that pays back first.
- Keep refactoring commits behaviour-neutral and separate from feature changes, avoid the big-bang rewrite, and agree the sequence with the people who own and deploy the module.
Follow-up
- How do you get a class under test when it calls a static singleton and the current time?
- How would you justify the refactor to a product manager who wants features instead?
- When is a full rewrite the right call?
Given a snippet of code, how would you analyze it to identify potential security flaws or performance bottlene
Medium · Practical Problem-Solving and Code Analy
Given a snippet of code, how would you analyze it to identify potential security flaws or performance bottlenecks?
Approach
- Trace every untrusted input (query params, body, headers, uploaded files) to its sinks: SQL, shell commands, file paths, HTML output, deserialization, redirects. String-built SQL is injection; the fix is a bind parameter in a prepared statement, not escaping. Also check path traversal and contextual output encoding against XSS.
- Check access control and data exposure: does the endpoint verify the caller owns the requested record (insecure direct object reference)? Look for hard-coded secrets, sensitive data written to logs, responses returning whole entities, and request bodies bound straight onto models (mass assignment).
- Do a performance pass: nested loops with list membership tests are O(n*m) and become O(n+m) with a hash set; queries inside loops are N+1; Java String += in a loop copies each time and is quadratic overall (use StringBuilder); unbounded reads into memory and missing pagination; unclosed connections or files (use try-with-resources or context managers).
- Check correctness under load: shared mutable state, check-then-act races, swallowed exceptions and missing timeouts on outbound calls.
- Rank findings by severity and likelihood, give the minimal fix for each, and say how you would verify it: a test with a malicious input for security issues, a profiler or benchmark for performance ones.
Follow-up
- Why is escaping user input weaker than a parameterized query?
- The code is correct but slow under production load. How would you find the hot spot?
- How would you stop this class of bug from coming back (static analysis, linters, tests in CI)?
How do you troubleshoot and optimize a sluggish database query in a production environment?
Medium · Technical and Architecture Fundamentals
Approach
- Scope it before touching SQL: is one statement slow or is everything slow? Use pg_stat_statements in PostgreSQL or the slow query log in MySQL to rank by total time, calls and mean time, and check whether it regressed suddenly (deploy, plan change, data growth) or degraded gradually.
- Read the real plan with EXPLAIN (ANALYZE, BUFFERS). ANALYZE executes the statement, so for writes run it inside a transaction you roll back, or on a replica. Look for sequential scans on large tables, estimated versus actual row counts far apart, nested loops over many rows, and sorts or hashes spilling to disk.
- Fix the common causes: a missing or badly ordered composite index (equality columns first, then the range or sort column); non-sargable predicates such as a function on the column, a leading-wildcard LIKE or an implicit type cast; stale statistics (run ANALYZE); ORM N+1 queries; SELECT * on wide rows; OFFSET pagination that should be keyset.
- Check what surrounds the query: lock waits in pg_stat_activity and pg_locks, table bloat, a saturated connection pool, disk I/O, and parameter values that make one cached plan bad for skewed data.
- Ship safely and prove it: in PostgreSQL, CREATE INDEX CONCURRENTLY avoids blocking writes (it cannot run inside a transaction block), compare the same metric before and after, and account for the extra write cost every new index adds.
Follow-up
- An index exists on the filtered column but the planner still scans the table. What are the likely reasons?
- A nightly report aggregates millions of rows and is getting slower. What would you change beyond indexes?
- How do you confirm the new index did not hurt insert and update throughput?
How do you isolate and debug intermittent concurrency issues in a multithreaded application?
Medium · Practical Problem-Solving and Code Analy
Approach
- Characterise the symptom: wrong values, a hang, or a crash, and how often it happens under which load. On a hang, take thread dumps (jstack for the JVM, py-spy dump for Python); jstack reports Java-level deadlocks it detects, showing which threads hold and wait on which locks.
- Make it reproducible: a stress test with many threads and iterations, more cores, and injected delays at the suspected interleaving points; then shrink it to the smallest failing case.
- Use race tooling: Go's -race flag, ThreadSanitizer for C and C++, Helgrind under Valgrind, jcstress for Java concurrency tests. Log with thread ids and monotonic timestamps, knowing that extra logging changes timing and can hide the bug.
- Classify, then fix: an unsynchronized read-modify-write or check-then-act needs a lock held across both steps or an atomic compare-and-set; deadlock from inconsistent lock order needs one global order or tryLock with a timeout; visibility bugs need proper memory-model guarantees (volatile or locks in Java); non-thread-safe collections need concurrent ones or confinement.
- Prove the fix: run the same stress test enough iterations before and after to show the failure rate dropped to zero, keep it as a regression test, and search for the same pattern elsewhere in the codebase.
Follow-up
- How do you tell a deadlock from a livelock or from starvation?
- Why can adding a log line make the bug disappear?
- When would you choose an atomic or lock-free structure over a lock, and what does it cost?
Suggested practice checklist
The plan follows the two reported stages and the three question groups: Day 1 maps the loop, Days 2 to 6 cover design principles, databases, architecture, code analysis, and refactoring, concurrency and reviews, and Day 7 is a full spoken run.
Day 1 — Map the loop and your gaps
- Write the two reported stages in order and, beside each, what it covers as candidates describe it.
- List the requirements candidates describe (object-oriented language, SQL, system architecture with load balancers, SOLID) and rate yourself on each.
- Choose the object-oriented language you will use for every example and note the projects that show each requirement.
Deliverable: A one-page loop map, a self-rating grid of the requirements, and a chosen language with the projects that back each requirement.
Day 2 — Object-oriented design and SOLID
- Write a before-and-after Single Responsibility example from your own code, naming the change that used to break two concerns at once.
- Write one short snippet each for open/closed, Liskov substitution, interface segregation and dependency inversion.
- Outline how object-oriented principles hold up in a large enterprise codebase: module boundaries, interfaces and dependency injection.
Deliverable: One Single Responsibility story, four principle snippets and a short outline for the object-oriented-at-scale question.
Day 3 — Slow database queries
- Write the diagnosis procedure for a slow production query: capture it, read the plan, check selectivity, fix, re-measure.
- Work the practice question on replacing offset paging with keyset paging and say what index it needs.
- Work the practice question on why an owner filter ignores the listing index, and list the usual causes.
Deliverable: A written diagnosis checklist plus solved answers to both SQL practice questions.
Day 4 — Load balancing and high availability
- Draw a service behind a load balancer with health checks, a shared session store and a replicated database, and label what fails at each box.
- Write a one-page comparison of round robin, least connections and hash-based routing, plus sticky sessions versus stateless services.
- Add a spike-handling section covering queueing, caching and scaling out.
Deliverable: Two diagrams (normal path and failure path) and a one-page cheat sheet on balancing and session persistence.
Day 5 — Code analysis and algorithm optimization
- Write three short snippets: a string-built SQL query, a loop that queries per row, and a nested-loop duplicate check; annotate each with flaw, fix and complexity change.
- Work the practice question on overlapping job attempts and peak concurrency, saying the brute-force cost first.
- Work the practice question on a rolling failure rate per destination and state its time and memory bounds.
Deliverable: Three annotated snippets and two solved practice problems, each with stated complexity.
Day 6 — Refactoring, concurrency and code reviews
- Outline a refactoring plan for a tightly coupled module: characterisation tests first, then seams, then small extractions behind interfaces.
- Write the concurrency debugging sequence: thread dumps, shared state, race versus deadlock, stress reproduction.
- Write your code review story with the problem, the mechanisms you introduced and the result.
Deliverable: A refactoring plan, a concurrency checklist and a written code review story.
Day 7 — Full spoken run
- Give a short recruiter-screen introduction aloud, then answer the load balancing, slow query and code review questions without notes.
- Practise the deadline-versus-quality practice prompt as a behavioural story.
- Review the weakest answer from the week and rewrite it.
Deliverable: A recorded run of the introduction and three answers, plus one rewritten answer.
Behavioural preparation
Candidates report collaboration as part of the loop: working with product managers, architects and QA, handling feedback, and settling technical disagreements constructively. For this role, the stories that fit are code reviews, design principles in practice, legacy refactoring and production ownership. Keep the situation to a few sentences and spend the answer on the decision and its result.
- Tell me about a time a design shortcut that broke a SOLID principle made a later change painful. What did it cost and what did you change?
- Describe a time you worked on a tightly coupled legacy module. How did you decide what to change first and how did you protect existing behaviour?
- Tell me about a technical disagreement with a teammate or architect and how it was settled.
- Walk me through a production issue you traced to its cause and what you changed afterwards.
How do load balancers distribute traffic across multiple servers, and how do you handle session persistence?
Medium · Technical and Architecture Fundamentals
Approach
- Start with the two layers: a layer-4 balancer routes by IP and port and is cheap and fast, while a layer-7 balancer reads HTTP and can route by path, header or cookie and terminate TLS. Then name the algorithms: round robin, weighted round robin for uneven servers, least connections for uneven request cost, and hash-based or consistent hashing when requests should land on the same cache-warm node.
- Cover health: active probes against a health endpoint plus passive ejection after failed responses, a slow-start ramp for new instances, and connection draining so a deploy does not cut in-flight requests.
- Answer session persistence in two parts. Sticky sessions use a balancer-inserted cookie or a source-IP hash; they are simple but skew load, break when the pinned node dies, and clump users behind one NAT. The cleaner design makes services stateless: keep session data in a shared store such as Redis, or in a signed token, so any node can serve any request.
- Close with the trade-offs and edge cases: the balancer itself must be redundant (a pair with a floating address, or a managed multi-zone balancer), long-lived connections such as websockets skew least-connections, and a shared session store becomes a dependency you must size and replicate.
Follow-up
- What happens to sticky sessions when the pinned server is removed during a deploy?
- When would you pick least connections over round robin?
- How do you stop the load balancer from being a single point of failure?
What strategies do you use to conduct effective peer code reviews without slowing down delivery velocity?
Medium · Software Design Principles and Code Qual
Approach
- Use a real story about a review practice you introduced or changed, and open with the problem in one or two sentences: pull requests waiting too long, large changes nobody could review properly, or rework discovered late. Give the numbers from your own team (time to first review, size of change, rework); do not invent figures.
- Describe the mechanisms: small, focused pull requests; formatters, linters and CI checks that remove style comments from human review; a description stating intent, risk and test evidence; and a convention separating blocking comments from optional suggestions (for example a 'nit' prefix).
- Show how speed and quality were both protected: an agreed response expectation, a quick call when a thread passes two rounds of comments, and a rule for urgent fixes (review after merge with a follow-up) that you applied sparingly.
- Say what the result was and how you knew. Name one disagreement with a reviewer or author and how it was settled with evidence or a team convention. The mistake that sinks this answer is a vague claim like 'I review thoroughly and quickly' with no mechanism, or framing speed and quality as opposing sides.
Follow-up
- How do you handle a reviewer who blocks changes over personal style preferences?
- What do you do when a fix must ship before a full review is possible?
- How did you know the change to your review process helped?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?