cswork / libraryOpen practice workspace

Interview guides · AVIV Group

AVIV Group Software Engineer Interview Guide

AVIV Group Software Engineer interview guide: four reported rounds, questions on unit tests, code review and event-driven design, plus a 7-day plan.

The 60-second brief

AVIV Group runs digital real estate platforms in Europe, the property-search products where people look for homes and follow the property market across several countries. Candidates describe the Software Engineer role as building, scaling and maintaining those platforms: backend services, event-driven architectures, and cost-focused work under the FinOps label, plus refactoring older systems so new features fit. Android appears both in the nice-to-have requirements and in a reported code review question, so some teams work on mobile code. The role sits next to Product Managers and other engineers, turning business requirements into technical specifications, reviewing code and mentoring peers.

This guide is for candidates interviewing for a Software Engineer position at AVIV Group. It covers the four reported rounds, the ten reported questions (working in existing code, code review, tracking user events, event-driven architecture, technical debt, and three behavioral prompts) and a seven-day plan. It does not cover compensation, and the exact content varies by team and seniority.

Candidates report four rounds over roughly 3-5 weeks: Initial Screening, Technical Assessments (take-home assignments or live coding), Behavioral Discussions and Final Team Discussions. They also report that the process varies by team and seniority.

Focus areas

  • FinOps (Financial Operations)
  • System Design
  • Coding Challenges

Overview

The reported questions fall into three groups. The first works from code that already exists: fixing a unit test in a provided codebase, adding a method to provided code for a functional requirement, reviewing code against Android or backend best practice, and explaining how you would track specific user events. The second is architecture: who owns the source of truth while a system migrates to an event-driven design, how you produce a high-level design when time is short, and how you balance feature delivery against technical debt. The third is behavioral: past projects, a plan to improve a team's engineering practices, and advocating for a technical direction in a group. The topics candidates associate with the loop are system design, coding challenges, event-driven architecture, FinOps and cloud cost optimization.

For the coding questions, the practice that matters is reading unfamiliar code rather than writing from a blank file. The requirements candidates describe list Java, Kotlin or Go, automated testing and cloud-based architectures, so pick whichever of those you are strongest in and rehearse a fixed routine: run the failing test, read the failure message, reproduce it in the smallest form, decide whether the test or the code is wrong, fix the cause, then add the test that would have caught it. Extending provided code works the same way: read the neighbouring methods, match their conventions, handle empty and invalid input, and add tests next to the change. Candidates are also advised to ask clarifying questions before coding and to talk through their reasoning.

The architecture questions reward concrete mechanisms over vocabulary. A source-of-truth debate during an event-driven migration is a question about data ownership: which service may write each entity, how other services learn about changes (an outbox table or change-data-capture feeding a broker), how consumers stay idempotent, and how you reconcile when two copies disagree. Be ready to say where you accept stale reads and where you do not, since the reported guidance names consistency versus availability as a trade-off to discuss. Because FinOps and cloud cost optimization appear as topics, also be able to name the cost levers in a design, such as storage tiers, retention, data transfer, over-provisioned compute and chatty cross-service calls.

The behavioral prompts are about influence as much as delivery. Candidates describe the role as including code review, mentoring and collaboration with product managers, so prepare stories where you made a decision, explained it to people outside engineering, and changed how a team works. Candidates also report advice to state trade-offs for every technical choice and to stay flexible when constraints change, which means each story should include the option you rejected and why.

The AVIV Group interview process

Candidates report four rounds over about 3-5 weeks and say the process varies by team and seniority. The stages below describe what candidates report, not a published schedule.

Initial Screening

Candidates describe the first stage as a review of basic qualifications and fit, and one report says the process starts with a conversation about expectations and experience. Treat it as a structured walk through your background against the role's requirements: a modern language such as Java, Kotlin or Go, automated testing, and cloud-based architectures, with event-driven systems, FinOps or cloud cost work, and Android as extras. Have a short, factual career summary ready and be able to say which extras you have actually touched.

What to demonstrate

  • Basic qualifications and fit for the Software Engineer role
  • Alignment between your experience and the stated expectations
  • How clearly you summarise your background

How to prepare

  • Write a short career summary that goes through the requirement list in order (language, automated testing, cloud-based architecture) and names one project for each.
  • Mark which nice-to-have items (event-driven systems, FinOps or cloud cost optimization, Android) you can discuss with a concrete example, and which you should describe honestly as exposure only.
  • Prepare factual answers on location, notice period and compensation expectations, and ask which formats the later rounds take for your team and level.

Technical Assessments

Candidates report that this stage may be a take-home assignment, live coding sessions, or both. The reported coding-style questions in this loop start from existing code instead of a blank file: fixing a failing unit test, adding a method to provided code, reviewing code for Android or backend practice, and explaining event tracking. Whichever format you get, practise that way: read first, reproduce, change the minimum, test the change, and say what you are doing as you go.

What to demonstrate

  • Working code in a take-home or live coding format
  • Debugging and unit-test skill, a reported evaluation area for this role
  • Extending existing code without breaking behaviour that already works

How to prepare

  • Take a small repository in Java, Kotlin or Go, break one test on purpose, and practise the routine of reproducing, finding the root cause and fixing the code rather than the assertion.
  • Add a method to code you did not write, matching its naming and error handling, with tests for empty, null and boundary input.
  • Review three real pull requests aloud with comments labelled blocking or optional, covering at least one Android or backend change.
  • If you receive a take-home, keep commits small and add a short README that lists your assumptions, trade-offs and what you would do with more time.

Behavioral Discussions

Candidates describe these conversations as aimed at your professional trajectory and your problem-solving methodology. The reported behavioral prompts ask about past project experience and how it prepared you for the role, how you would plan improvements to a team's engineering practices, and a time you advocated for a technical direction. Answer with decisions you personally made, the alternatives you weighed and a result you can defend.

What to demonstrate

  • Professional trajectory and how your past projects connect to this role
  • Problem-solving methodology as you describe it in real situations
  • Communication of decisions and trade-offs

How to prepare

  • Choose three projects and write each as context, your decision, the rejected alternative, the result and what you would change.
  • Prepare a plan for improving a team's practices that starts from a measured problem such as flaky tests or slow reviews.
  • Rehearse the advocacy story out loud, including the strongest argument against your position.

Final Team Discussions

Candidates describe the final interviews as focused on strategic and cultural alignment with the team. Because the stage is strategic, rehearse the strategy-flavoured reported questions as well as your stories: balancing immediate feature delivery against long-term technical debt, giving a high-level design when time is short, and settling a source-of-truth debate in an event-driven migration. Expect the conversation to depend on the team and seniority.

What to demonstrate

  • Strategic thinking about engineering decisions
  • Alignment with the team you would join
  • How you handle trade-offs and disagreement in a group

How to prepare

  • Write a short policy for splitting time between features and debt, with a rule for what counts as debt worth paying down now.
  • Practise a design walk-through that begins with scope and constraints, names the data owner for each entity, and states the trade-off you accept.
  • Prepare questions about how the team reviews code, tests changes and decides on architecture, so the conversation runs both ways.

Where candidates lose points

Making a failing unit test pass by loosening the assertion or special-casing the input without finding why it failed

Reproduce the failure, read the message, and decide out loud whether the test or the code is wrong. Fix the cause, then add a test that fails without the fix.

Giving a code review that lists style nits, or says the change looks fine, while a null case or error path goes unmentioned

Review in priority order: correctness, data and security, tests, then readability. Label each comment blocking or optional and write down the reason for each blocking one.

Answering the source-of-truth question with 'use events' without naming who owns each piece of data

State one writer per entity, how changes reach other services (outbox or change-data-capture), how consumers handle duplicates, and how you reconcile disagreement. Say where stale reads are acceptable.

Presenting one architecture as the only answer, with no trade-offs, then having no response when the constraints change

Give the option you choose, the one you rejected and the cost of each. Candidates are advised to stay flexible, so say what new constraint would make you switch.

Telling behavioral stories in 'we' with no personal decision, alternative or measurable result

Write each story as your decision, the rejected option, the outcome and one change you would make. Cut any story you cannot attach a result to.

Questions & practice

Track a rolling failure rate per destination for circuit decisions

Easy · sliding window · ring buffer · circuit breaker

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

Approach

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

Follow-up

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

Diff a projection against the primary without per-row point reads

Hard · reconciliation · range hashing · throttling

The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.

Approach

  1. Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
  2. Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
  3. For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
  4. Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.

Follow-up

  • The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
  • Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

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

  1. 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.
  2. 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.
  3. 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.
  4. 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?

Fix the unit test provided in the existing codebase.

Medium · Technical and Domain Proficiency

Approach

  1. Run the failing test on its own first, then read the assertion message and the stack trace. Before changing anything, decide whether the test or the production code is wrong: check the requirement, the method's doc comment and the git log of both files.
  2. Go through the usual causes in order: a wrong expected value, a stale mock that no longer matches the method signature or its argument matchers, shared mutable state that makes the result depend on test order, wall-clock time or time zone, iteration order of a hash map, exact equality on floating-point values, and async work the test never waits for.
  3. Make the smallest fix that keeps the test meaningful. Never loosen the assertion just to get a green run. If the production code is at fault, fix it, keep the test and add a regression case for the input that exposed the bug.
  4. Remove nondeterminism at its source. Inject a java.time.Clock instead of calling now(), seed any randomness, and compare doubles with a tolerance, for example JUnit 5 assertEquals(expected, actual, delta).
  5. Check the fix by running the whole suite and then repeating the test in a random order (the JUnit 5 random method orderer, or go test -count=N -shuffle=on -race in Go). That shows the bug is gone and no test was coupled to another.

Follow-up

  • With no written spec, how do you decide whether the test or the code under test is wrong?
  • The test passes on your machine but fails intermittently in CI. What do you check first?
  • How would you rewrite this test so the next refactor cannot break it for the wrong reason?

Add a new method to the provided code to solve a specific functional requirement.

Medium · Technical and Domain Proficiency

Approach

  1. Before writing anything, read the class you are extending: its naming, its error-handling style (exceptions, a result type, or nullable returns in Kotlin), whether it uses immutable data, and its existing callers and tests. Restate the requirement with a concrete input and output, then ask about empty input, nulls, duplicates and boundary values.
  2. Write a failing test for the main case and one for each edge case you agreed on, then implement just enough to pass them and refactor while they stay green. Reuse the class's existing helpers instead of copying their logic.
  3. Choose the data structure on purpose and say what it costs. For example, building a hash map once gives O(1) average lookups, while scanning a list on every call is O(n) per call, which can add up to O(n²) overall. State the method's time and space complexity.
  4. Keep the change additive. Existing method signatures and behaviour stay as they are, invalid input is rejected explicitly in the class's usual style, and if the object is shared between threads you say whether the new method is safe to call concurrently.
  5. Run the full suite to show there is no regression, and name any cleanup you noticed but kept out of this change so the diff stays easy to review.

Follow-up

  • The requirement gains a second variant next sprint. Would you add a parameter, an overload or a strategy object, and why?
  • How does your method behave if the input no longer fits in memory?
  • Which test did you write first, and why that one?

Explain the logic behind your approach to tracking specific user events.

Medium · Technical and Domain Proficiency

Approach

  1. Start from the product question the event has to answer, then define the event contract: a client-generated event_id (UUID) for deduplication, the event name, a user or anonymous id, a session id, client and server timestamps, a schema version and typed properties. A property site gives easy examples, such as a listing view or a contact click.
  2. Describe the pipeline. The client SDK batches events and retries, a collection endpoint validates them, and they go to an append-only log keyed by user id so each user's events stay in order within a partition. Stream consumers and the warehouse read from that log. Delivery is at-least-once, so every consumer dedupes on event_id.
  3. Protect data quality with schema validation at ingestion, a quarantine for malformed events, and processing by event time with watermarks so late mobile events land in the right window. Keep the server receive time as well, because client clocks drift.
  4. Treat privacy as part of the design. Track only after consent where the law requires it, which includes GDPR in Europe. Keep PII out of free-form properties, use pseudonymous ids and set a retention period.
  5. Validate the numbers. Reconcile event counts against the backend record of the same action, such as the stored contact request. Track business-critical events server-side, because ad blockers and dropped connections lose client events.

Follow-up

  • A mobile client retries after a timeout and the event arrives twice. Where and how do you deduplicate?
  • How do you change the event schema without breaking the dashboards and jobs that already read it?
  • For this event, would you track it client-side or server-side, and what does each option lose?

In the context of migrating to an event-driven architecture, how would you resolve debates regarding the "sour

Medium · System Design and Architecture

In the context of migrating to an event-driven architecture, how would you resolve debates regarding the "source of truth"?

Approach

  1. Settle the debate with an ownership rule: each entity has exactly one service that accepts writes and enforces its invariants, and every other service keeps a read-only projection built from that owner's events. The source of truth is whichever service validates the write. A shared table or the broker cannot be the source of truth.
  2. Decide for each domain whether events are the record (event sourcing, which gives an audit trail and replay but is more complex) or notifications about state held in a database (state-based). Most domains only need the second.
  3. Name the dual-write problem: committing to the database and publishing to the broker is not atomic. Solve it with a transactional outbox, where the event row is written in the same transaction and a relay publishes it, or with change data capture that reads the database log. Consumers must be idempotent because delivery is at-least-once.
  4. Migrate with the strangler pattern. The legacy system stays the owner at first while new services consume its change stream. Then move ownership one entity at a time, with a reconciliation job that compares each projection with the owner and an architecture decision record for every handover.
  5. State the consistency cost. Projections are eventually consistent, so read-your-writes paths read from the owner or check a version number. Partitioning by aggregate id keeps events for each entity in order.

Follow-up

  • Two services both write to the same customer table today. How do you split ownership without downtime?
  • A projection has drifted from the owner's data. How do you detect it, and how do you rebuild it?
  • When would you choose not to use event sourcing for a domain?

Describe your approach to high-level architectural design under time constraints.

Medium · System Design and Architecture

Approach

  1. Pin down scope before you draw anything. Pick the two or three core use cases and the non-functional requirements: read and write volume, latency target, consistency needs and availability. Say out loud which features you are leaving out.
  2. Do a quick estimate to size the design: average QPS is daily requests divided by 86,400, and peak is a multiple of that. Add a storage figure per year. Use the numbers to decide whether one relational database and a cache are enough before you add components.
  3. Sketch the end-to-end skeleton first: client, load balancer or gateway, stateless services, primary store, cache and an async queue for slow work. Then go deep on the one or two components with the hardest constraint, rather than detailing everything a little.
  4. State every major choice with its trade-off: SQL or NoSQL, synchronous calls or messaging, and consistency or availability when the network partitions. Note what you are deferring, so that time pressure looks like a deliberate scoping decision.
  5. Finish with failure modes and evolution: which component fails first, how you would observe it (metrics, traces, alerts), and what changes at ten times the load.

Follow-up

  • If you could detail only one component, which would you pick and why?
  • What did you deliberately leave out, and what risk does that leave open?
  • How does the design change if the read-to-write ratio flips?

How do you balance immediate feature delivery with long-term technical debt reduction?

Medium · System Design and Architecture

Approach

  1. Make the debt visible and priced. List each item with its ongoing cost (incidents, slower delivery in that module, on-call pages, cloud spend) and its cost to fix. Rank items by that ongoing cost times how often the team touches the code, not by how ugly the code is.
  2. Pay debt down through feature work where you can. Refactor the module you are about to change before adding the feature, leave code a little cleaner than you found it, and agree a recurring capacity share with product so the work is not renegotiated every sprint.
  3. Take shortcuts deliberately. Record each one in a ticket or decision record with the trigger that will force repayment, and contain it behind tests and a feature flag so it cannot spread.
  4. Measure the effect so the argument rests on data: the four DORA metrics (deployment frequency, lead time for changes, change failure rate, time to restore service), plus infrastructure cost when the debt is an over-provisioned or wasteful legacy component.
  5. Give a concrete example: a legacy module replaced piece by piece with the strangler pattern while features kept shipping, and the metric that improved as a result.

Follow-up

  • Product wants the feature this sprint, and the module it touches has no tests. What do you do?
  • How would you convince a product manager that a refactor is worth a sprint?
  • Tell me about a piece of debt you chose not to pay down, and why.

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

  1. 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.
  2. 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.
  3. 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.
  4. 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

Each day targets one reported question type or round: working in existing code, code review, event tracking and event-driven design, then cost, debt and behavioral stories.

Day 1 — Map the loop against your background

  • Write the four reported rounds in order and mark which one you feel least ready for.
  • List the must-have requirements (Java, Kotlin or Go, automated testing, cloud-based architecture) and the nice-to-have ones (event-driven systems, FinOps or cloud cost work, Android), and write one real example or an honest gap for each.
  • Choose three projects for the project-history and advocacy stories and write a one-line decision and result for each.

Deliverable: A one-page round map, a requirement-by-requirement evidence list, and three project one-liners.

Day 2 — Fix and extend existing code

  • Take a small repository in your strongest language, break one test, and fix the root cause while narrating each step aloud.
  • Add a new method to code you did not write, following its conventions, with tests for empty, null and boundary input.

Deliverable: One bug-fix commit with a reproduction note, one feature commit with tests, and a written solution for each of the two practice problems.

Day 3 — Code review for Android or backend

  • Review three real pull requests aloud, at least one backend and one Android, and write every comment with a blocking or optional label.
  • Build a personal checklist ordered correctness, data and security, tests, readability, with backend items (idempotency, timeouts, N+1 queries) and Android items (main thread, lifecycle, state loss).
  • Pick your three most frequent comments and note which linter or CI check would remove the need for them.

Deliverable: Three written reviews, a one-page review checklist, and a list of three checks to automate.

Day 4 — Tracking user events and event-driven basics

  • Design an event schema for tracking user actions in a listing-search flow: event name, id, timestamp, schema version, user or session identifier and consent handling.
  • Decide how events are delivered and deduplicated, and where an event can be lost or arrive twice.

Deliverable: A written event schema with a delivery and deduplication note, and a worked solution to the projection-diff problem.

Day 5 — Source of truth and design under time pressure

  • Write a migration plan for one entity moving from direct database calls to events: single writer, outbox or change-data-capture, idempotent consumers, reconciliation job, and where stale reads are accepted.
  • Practise a high-level design for a service of your choice with a fixed timer, in this order: scope, data owners, read and write paths, failure modes, trade-offs.

Deliverable: A one-page migration plan, one timed design write-up, and the concurrent-edit solution.

Day 6 — Technical debt, cloud cost and improving practices

  • Write a policy for balancing feature delivery and technical debt: how debt is recorded, what triggers paying it down, and how you explain the cost to a product manager.
  • List the cost levers in a typical cloud design (storage tiers and retention, data transfer, over-provisioned compute, chatty service calls) and give one example of measuring each.
  • Write a plan to improve one engineering practice, with a baseline metric, a pilot, a checkpoint and a stop condition.

Deliverable: A debt policy, a cost-lever list with measurements, a practice-improvement plan, and the index question solution.

Day 7 — Behavioral rehearsal and full mock

  • Tell the past-projects story and the advocacy story aloud, including the rejected option and the strongest counter-argument.
  • Run a mock in which you answer one event-driven design question, review a short piece of code, and answer one behavioral prompt back to back.
  • Write the questions you will ask the team about reviews, testing and architecture decisions.

Deliverable: Recorded or written final versions of two stories, notes from the mock, and a list of questions for the team.

Stories for the project and team rounds

Candidates describe the behavioral and final team conversations as covering professional trajectory, problem-solving method, and strategic and cultural alignment with the team. For a role that includes code review, mentoring and working with product managers, the strongest stories show a decision you made, the alternatives you weighed, how you persuaded or listened to others, and a result you can state.

  1. How do you balance immediate feature delivery with long-term technical debt reduction?
  2. Tell me about a time a test or code review caught a defect before it reached users.
  3. Describe a time you reduced the cost or complexity of a system you owned.

Perform a code review focusing on Android or backend best practices.

Medium · Technical and Domain Proficiency

Approach

  1. Start by saying what the change is meant to do, based on its description and tests, before commenting on any line. Then review in priority order: correctness first (null and empty input, off-by-one, error paths, resource cleanup), then security and data handling, then tests, then design and readability.
  2. For backend code, look at transaction boundaries, retry safety (is the handler idempotent if a request or event is delivered twice), N+1 queries, missing timeouts, unbounded result sets, and backwards-compatible API or schema changes. In Kotlin or Go, check coroutine or goroutine lifetimes, context cancellation and ignored errors.
  3. For Android code, look at work on the main thread, lifecycle-aware collection of streams, references to Activities or Views that outlive them, state loss on configuration change, and scope choice for coroutines.
  4. Check the tests: would they fail without the change, do they cover the edge cases, and do they depend on time, network or ordering? A change with no test for its main branch is a review finding by itself.
  5. Write each comment so it can be acted on: quote the line, name the risk, suggest a fix, and mark it blocking or optional. Say what is good about the change as well, and ask a question when you are unsure rather than asserting a bug.

Follow-up

  • Which of your comments would block the merge, and which would you leave as suggestions?
  • The author disagrees with your main comment. How do you resolve it?
  • What would you automate with a linter, static analysis or a CI check so a person never has to write that comment?

Discuss your past project experiences and how they prepared you for this role.

Medium · Behavioral and Strategic Alignment

Approach

  1. Pick two or three projects that map onto the requirements candidates describe: backend services in Java, Kotlin or Go, automated testing, cloud-based architecture, and event-driven or cost work if you have it. For each, give two sentences of context, then spend the time on what you personally decided.
  2. Make the link to this role explicit in one sentence per project, for example how a migration you ran taught you to keep consumers idempotent, or how a flaky test suite led you to change how you test.
  3. Include one trade-off per project: the option you rejected, what it would have cost, and why the choice you made fit the constraints at that moment.
  4. Close with a result you can defend (a measured change, an incident that stopped recurring, a release that became safer) and one thing you would do differently now.
  5. The mistake that sinks this answer is a chronological tour of employers and technologies. Use 'I' for your own work and name teammates' contributions separately.

Follow-up

  • Which part of that project was yours rather than the team's?
  • What was the hardest trade-off, and what did you give up by choosing as you did?
  • Which of your projects is least like this role, and what carries over from it?

How do you approach providing a strategic plan to improve engineering practices within a team?

Medium · Behavioral and Strategic Alignment

Approach

  1. Begin with diagnosis, not tools. Gather evidence of the actual problem: escaped defects, flaky or slow tests, review turnaround, repeated incident causes. Pick one or two problems and record a baseline number for each.
  2. State a measurable goal and a small first step the team can try, such as a pilot on one service: tests required on changed code, a short review checklist, a CI gate for the flakiest suite, or a written decision record for architecture changes.
  3. Cover buy-in: talk to the engineers affected, explain the cost in their time, make the pilot reversible, and align the timing with the delivery commitments of product managers.
  4. Roll out in phases with owners and a checkpoint. Review the metric at the checkpoint, keep what moved it, and drop what did not.
  5. The mistake is a wish-list of tools or a mandate with no baseline. Show that you would measure first, change little, and let evidence decide what spreads.

Follow-up

  • A senior engineer says this will slow delivery. How do you respond?
  • Which metric would you check after the first checkpoint, and what result would make you stop?
  • How would the plan change if you were a team member rather than the lead?

Describe a time you had to advocate for a specific technical direction within a group setting.

Medium · Behavioral and Strategic Alignment

Approach

  1. Choose a decision with real alternatives, such as a message broker, a migration approach, a testing strategy or a build-versus-buy call, where reasonable people disagreed. State your position and the criteria you used.
  2. Show the evidence you brought to the group: a prototype, a benchmark, an incident history or a cost estimate. Say how you presented the trade-offs, including the costs of your own option.
  3. Describe how you handled the opposing view: state it fairly, say what would have changed your mind, and adjust the proposal where the objection was right.
  4. Report the outcome honestly. The group may have adopted your plan, a mix, or the other option; if you were overruled, say how you committed to the decision and what you did to make it succeed.
  5. The mistake that sinks it is a story in which you were right and everyone else was slow. Candidates are advised to show flexibility, so include a point where you changed your position.

Follow-up

  • What evidence would have changed your mind?
  • How did you explain the decision to people outside engineering?
  • What happened afterwards with the colleagues who disagreed?