cswork / libraryOpen practice workspace

Interview guides · Arrise solutions

Arrise solutions Software Engineer Interview Guide

Arrise solutions Software Engineer interview guide: five reported rounds, coin change style DP, Java multithreading questions and a seven-day prep plan.

The 60-second brief

Candidate reports describe Arrise solutions as hiring Software Engineers to build and maintain services that carry heavy traffic and large data volumes, inside a product ecosystem the reports do not break down further. The role is described as working with product managers and other engineers on requirements and scope, keeping code quality up through testing, code review and early debugging, and joining architecture conversations about performance and long-term maintainability. The reports name no specific products, customers or teams, so this guide stays with the engineering topics they do list: Java, data structures, dynamic programming, multithreading and debugging.

This guide is for engineers preparing for the Arrise solutions Software Engineer loop. The requirements candidates describe ask for an object-oriented language such as Java, solid data structures and algorithms, and hands-on multi-threaded programming, with distributed systems and cloud infrastructure as extras. Reports also mention four or more years of professional experience among successful candidates. The guide covers all five reported rounds, with most of its depth on dynamic programming, Java concurrency, debugging your own code, and the design, culture and leadership conversations.

Candidates report five rounds over roughly four to six weeks: Technical Screening, Algorithmic Skills Assessment, System Design Discussion, Cultural Fit Evaluation and Leadership Evaluation. The reported order moves from coding fundamentals to architecture and ends with a leadership and decision-making conversation.

Focus areas

  • Java
  • Multithreading
  • Dynamic Programming (DP)

Overview

The reported work is service engineering in Java: code that has to stay correct when many threads touch it and stay fast when traffic and data grow. That shapes the question mix. Candidates describe two clusters of coding questions. One is dynamic programming and algorithms: a question in the style of Coin Change 2 (counting combinations of coins), array problems that need careful state management, and optimisation problems solved with memoization or an iterative table. The other is multithreading: a thread-safe program that prints the numbers from a to b using several threads, producer-consumer patterns or custom synchronizers, and debugging race conditions or deadlocks in Java code.

Preparation should therefore be hands-on. For the DP cluster, be able to move between top-down memoization and a bottom-up table, state time and space cost, and shrink a table to one dimension when the recurrence allows it. For the concurrency cluster, write compiling Java without an IDE crutch: synchronized with wait and notifyAll inside a while loop, ReentrantLock with Condition, Semaphore, and BlockingQueue, and know when each fits. Reading code is not enough; run your own programs many times, because a race that passes once proves nothing.

The loop does not stop at coding. Reports list a System Design Discussion, a Cultural Fit Evaluation and a Leadership Evaluation, and the requirements candidates describe mention participating in architecture discussions and working with product managers. Reported tips are consistent about habits: talk through your reasoning, test your solution against edge cases before calling it done, and be able to debug and iterate on your own work. Memorised answers are a weak plan, since a variant such as minimum coins instead of combinations, or a different number of threads, breaks a recited solution. Practise the variants as deliberately as the originals.

The Arrise solutions interview process

Candidates report five stages across roughly four to six weeks, each described in a single line. The preparation advice below is built from those stage names and from the question types and topics candidates mention.

Technical Screening

Candidates describe Technical Screening as the opening assessment of technical skills and problem-solving ability. Nothing more specific is reported about its format, so treat it as the first check on the fundamentals the role lists: an object-oriented language, data structures and algorithms, and some multi-threaded programming. Prepare by making your main language fluent enough that syntax never slows you down, and by solving one coin-change-combinations problem and one small thread program from a blank file.

What to demonstrate

  • Problem-solving ability, which is how the stage is described
  • Command of an object-oriented language such as Java
  • Working knowledge of data structures and algorithms

How to prepare

  • Solve a Coin Change 2 style problem from scratch in Java: dp[0] = 1, coins in the outer loop, amounts in the inner loop, O(coins x amount) time and O(amount) space, then explain why swapping the loops counts orderings instead of combinations
  • Re-solve it as a variant (fewest coins, or each coin usable once) so a changed problem does not throw you
  • Write a two-thread program in Java that alternates output, using only what you can recall without looking anything up

Algorithmic Skills Assessment

Candidates describe the Algorithmic Skills Assessment as testing algorithmic skills through coding challenges. Reports on algorithmic problem solving mention dynamic programming and recursion, time and space analysis, and edge-case handling such as empty or null input, plus questions on reducing the memory footprint of a DP solution. Approach it by stating the recurrence and base cases before coding, naming complexity out loud, and walking a small input through your code by hand at the end.

What to demonstrate

  • Working code for algorithmic problems, since the stage is coding challenges
  • Dynamic programming and recursion patterns, with time and space analysis
  • Edge-case and input-validation handling

How to prepare

  • For each DP problem, write both the memoized recursion and the bottom-up table, then compress the table (a 2D grid to one row, a 1D array kept as it is) and say what you lose by doing so
  • Solve array problems that carry state across a scan, and for each list the empty, single-element, all-equal and overflow cases before you write code
  • Practise the sentence 'brute force is X, the repeated work is Y, so the table is indexed by Z' until it comes out in one breath

System Design Discussion

Candidates describe the System Design Discussion as a conversation about system design and architecture. The requirements they cite include designing services for significant traffic and data, with distributed systems and cloud infrastructure as nice-to-have experience. Approach it by clarifying requirements and scale, sketching the request path and data model, then discussing where the bottleneck sits and what you would trade away to fix it.

What to demonstrate

  • Architectural reasoning in a discussion format
  • How you scale a service handling heavy traffic and large data
  • How you explain trade-offs and long-term maintainability

How to prepare

  • Practise one design from a clear skeleton: requirements, API, data model, request path, scaling, failure handling, and what you would measure
  • Connect your concurrency knowledge to design: a bounded queue between producers and consumers inside a process becomes a message queue with back pressure between services
  • Rehearse two designs where retries are common, such as a resumable export or a bulk write a script may repeat, and say how idempotency keys make retries safe

Cultural Fit Evaluation

Candidates describe the Cultural Fit Evaluation as an assessment of how you align with the company's culture and values; the reports do not list those values or the format. The role description does mention working with product managers and other engineers, code review and debugging, so draw your stories from those situations. Prepare concrete examples with a specific situation, your own action and a result, rather than general statements about teamwork.

What to demonstrate

  • How you describe working with engineers and product managers
  • How you talk about code review, testing and quality
  • Whether your examples show your own actions and outcomes

How to prepare

  • Write three short stories: a disagreement over scope with a product manager, a code review you handled well or badly, and a bug you chased to its cause
  • For each story, write the situation in one sentence, then your action in the first person, then the measurable result and what you would change
  • Say 'I' for what you did and name teammates for what they did, so your contribution is clear

Leadership Evaluation

Candidates describe the Leadership Evaluation as the final assessment, focused on leadership qualities and decision-making, and one summary of the loop calls it the final hiring manager discussion. The role description mentions taking ownership of technical design, so choose examples where you made or shaped a decision. Approach it by showing the options you weighed, what you chose, what it cost, and how you followed up.

What to demonstrate

  • Leadership qualities, as the stage is described
  • Decision-making and ownership of technical design
  • How you reason about trade-offs and their consequences

How to prepare

  • Prepare a design or technology decision you owned: the alternatives, the deciding evidence, what you gave up, and what happened afterwards
  • Prepare a story where you disagreed with a decision, lost, and still committed, including what you did to reduce the risk you had named
  • Write down two questions about the team's engineering practices that you actually want answered

Where candidates lose points

Counting permutations when the question asks for combinations in a Coin Change 2 style problem

Put the coin loop on the outside and the amount loop on the inside so each combination is counted once, and check with coins [1, 2] and amount 3, which has two combinations, not three. Also initialise dp[0] = 1 and test amount 0 and an empty coin list before you finish.

Writing the a-to-b printing program with an unsynchronised shared counter, an if instead of a while around wait, or no final notifyAll

Guard the counter with one lock, wait inside a while loop that rechecks whose turn it is, and call notifyAll after every increment, including the last one so threads waiting past b can exit. Run it with different thread counts and ranges, including a > b and a single thread, and confirm it terminates every time.

Calling a concurrency fix done after one clean run, or hiding a race with sleeps and extra synchronized blocks

Name the shared state and the invariant it breaks before touching code, such as a lost update in count++ or a check-then-act gap. Loop the test many times with more threads than cores, and fix with the narrowest lock or an atomic type. For a deadlock, take a thread dump, read the lock cycle, and impose one lock order or use tryLock with a timeout.

Reciting a memorised solution that fails as soon as the problem changes

Practise every problem with its variants: minimum coins, bounded coin counts, a different number of printing threads, a bounded versus unbounded buffer. Explain the recurrence or the invariant first so you can adjust it, rather than reproducing code from memory.

Answering culture and leadership prompts with generalities, or with 'we' throughout, so your own decision never appears

For each story, state the options, your choice, the cost you accepted and the result, using 'I' for your actions. Include one story where the outcome was not good and say what you changed afterwards.

Questions & practice

Similar to Coin Change 2 (calculating combinations of changes).

Medium · Dynamic Programming and Algorithms

Approach

  1. Define dp[x] as the number of coin combinations that sum to x, set dp[0] = 1 (the empty combination), and fill a single long[] of size amount + 1.
  2. Loop over coins on the outside and amounts on the inside, from coin up to amount, adding dp[x - coin] into dp[x]. Fixing the coin order this way counts each multiset once. Swapping the two loops counts ordered sequences instead, which is a different problem (Combination Sum IV).
  3. Cost is O(n * amount) time and O(amount) space. Explain the move from the 2D table dp[i][x] (first i coins) to one row: iterating x upward lets each coin be reused without limit.
  4. Edge cases: amount 0 returns 1, an empty coin list with a positive amount returns 0, coins larger than the amount add nothing, and duplicate coin values have to be removed or they double-count. Counts can overflow int, so use long or take a modulus if one is given.

Follow-up

  • How does the recurrence change if you want the minimum number of coins rather than the number of combinations, and what do you return when no combination exists?
  • If each coin can be used at most once, or at most k times, which loop direction or extra dimension do you need?
  • How would you return one actual combination, or list all of them, and what does listing them cost?

Problems involving complex array manipulations and state management.

Medium · Dynamic Programming and Algorithms

Approach

  1. Pin down the contract first: can you modify the array in place, are values bounded or negative, is it sorted, and should you return indices or values. Each answer rules some techniques in or out.
  2. Pick the pattern from the access shape. Range sums use prefix sums (O(n) to build, O(1) per query). Contiguous windows under a constraint use a sliding window with a HashMap of counts. Pair searches on sorted data use two pointers. In-place partitioning uses a Dutch national flag swap in one pass.
  3. When the answer depends on what happened earlier, name the states explicitly and carry them in variables. Stock trading with a cooldown, for example, carries hold, sold and rest values per day, so it runs in O(n) time and O(1) space with no table.
  4. Edge cases: empty and single-element arrays, all-negative input (start Kadane's running best from the first element, not from 0), duplicates in two-pointer scans, and int overflow in running sums. Trace a short example by hand before you call the code finished.

Follow-up

  • Can you do this in place with O(1) extra space, and what does that cost you in readability or in the number of passes?
  • What changes if the array arrives as a stream and you only see each element once?
  • Which input makes your sliding window or two-pointer loop do the most work, and is it still linear?

Optimization challenges requiring memoization or iterative DP approaches.

Medium · Dynamic Programming and Algorithms

Approach

  1. Start from the recursive brute force and state the subproblem in words (for example, best value using items i..n with capacity c). Name the parameters that make up the state, the recurrence and the base cases before you write any code.
  2. Top-down: add a memo keyed by the state, either an array sized to the parameter ranges or a HashMap for sparse states. Time is the number of states times the work per state. In Java, deep recursion can throw StackOverflowError, which is a practical reason to switch to tabulation.
  3. Bottom-up: fill the table in an order where every dependency is computed first. Then cut memory with a rolling array when a row depends only on the previous one. LCS drops from O(m*n) to O(min(m, n)) space. 0/1 knapsack drops to one array if capacity is iterated downward, so no item is reused.
  4. Name the trade-off: memoization computes only reachable states and is quicker to write, while tabulation avoids recursion overhead and makes space reduction easy. A rolling array loses the full table, so reconstructing the chosen path needs parent pointers or a second pass.

Follow-up

  • The table only ever reads the previous row: how far can you cut its memory, and what do you give up?
  • How would you recover the actual choices (items, path or split points) and not just the optimal value?
  • If one state dimension is very large but sparse, would you keep an array memo or switch to a map, and why?

Designing a thread-safe program to print numbers from a to b using multiple threads.

Medium · Multithreading and Concurrency

Approach

  1. Restate the goal as strict ordering across N threads: thread k prints the values where (value - a) % N == k, and one shared counter, guarded by a single lock, holds the next value to print.
  2. With synchronized: each thread loops, waits while it is not its turn and the counter is <= b, prints, increments and calls notifyAll. The wait must sit in a while loop because of spurious wakeups and wrong-thread wakeups. Plain notify can wake a thread whose turn it is not and leave every thread waiting.
  3. For a precise handoff, use a ReentrantLock with one Condition per thread and signal only the next one, or a chain of Semaphores where thread i acquires sem[i], prints and releases sem[(i + 1) % N], with only sem[0] starting at 1 permit.
  4. Termination is the usual bug: once the counter passes b, every thread must wake and exit, so the last printer signals all of them. Edge cases: a > b prints nothing, N larger than the range leaves some threads idle, and N = 1 must still work. Say plainly that this is slower than a single thread; the exercise is about coordination, not throughput.

Follow-up

  • Why does the wait sit in a while loop and not an if, and what goes wrong if you call notify instead of notifyAll?
  • How would you rewrite it with an AtomicInteger and no locks, and what does the busy-waiting cost in CPU?
  • How do you make sure every thread exits cleanly, and none is left blocked, when the range runs out or a thread is interrupted?

Implementing producer-consumer patterns or custom synchronizers.

Medium · Multithreading and Concurrency

Approach

  1. Start with the library answer: a bounded ArrayBlockingQueue whose put blocks when the queue is full and whose take blocks when it is empty. offer and poll with a timeout give back-pressure without blocking forever. Point out that the bound is what stops a fast producer from exhausting memory.
  2. Then write the bounded buffer yourself: a circular array with head, tail and count fields, one ReentrantLock, and two Conditions (notFull, notEmpty). Each side waits in a while loop and signals the other condition after changing count. With synchronized there is a single wait set shared by producers and consumers, so you need notifyAll.
  3. Shutdown: send one poison-pill item per consumer, or interrupt the workers. When you catch InterruptedException, restore the flag with Thread.currentThread().interrupt() rather than swallowing it.
  4. For a custom synchronizer, build it from the same parts. A counting semaphore is a permit count plus a lock and a condition, and a one-shot latch is a count that wakes all waiters when it reaches zero. Mention AbstractQueuedSynchronizer (tryAcquireShared / tryReleaseShared) as how java.util.concurrent builds these classes.

Follow-up

  • With several producers and consumers, what ordering guarantees do you still have, and how would you keep per-key order?
  • Consumers fall behind for minutes: what happens with your bound, and would you block, drop or spill to disk?
  • How would a single-producer, single-consumer ring buffer avoid locks altogether, and why do its indices need volatile or equivalent ordering?

Find version gaps and relay lag with window functions

Medium · window functions · event tables · frame semantics

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

Approach

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

Follow-up

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

Denormalise tenant onto revisions and backfill it live

Hard · denormalisation · online migration · backfill

resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.

Approach

  1. Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
  2. Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
  3. Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
  4. Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.

Follow-up

  • The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
  • How do you verify the backfill actually finished, given rows are still being inserted while it runs?

Design the async export contract a client can resume safely

Hard · async operations · idempotency · leases

A tenant asks for a CSV of every resource. The work runs for minutes on the worker fleet through a job_run row carrying a lease, an attempt count and a dedupe_key, far past the edge's 400 ms budget. Callers are a browser that polls and a script that walks away and checks later. Specify what the submit call returns, the operation resource and its states, how a duplicate submit is handled, how a client learns about completion, what cancellation means given that a lease can expire mid-run, and how the result is fetched and when it expires.

Approach

  1. Split the API in two. Submit returns 202 with an operation id and a location to poll, and never blocks on the work. The operation is a real resource with its own lifecycle - queued, running, succeeded, failed, cancelled - plus attempt, a monotonic progress figure, and a terminal error drawn from the same code taxonomy the synchronous endpoints use, so a client needs one error vocabulary rather than two.
  2. Deduplicate at submit using job_run.dedupe_key, unique over (job_type, dedupe_key) while status is 'queued' or 'running': a repeat submit of the same logical export returns 200 with the existing operation instead of 202 with a new one, and the partial index deliberately permits a legitimate re-run once the first has finished. Pair it with the request's idempotency key so an HTTP-level retry of the submit is exact rather than merely similar.
  3. Tell the poller how to poll: Retry-After on the polling response, a minimum interval enforced at the edge, and a documented maximum lifetime after which an operation is reaped. Polling is the contract of record; the webhook is the fast path, and both must lead to the same terminal state, so a client that receives the completion event and then polls anyway sees no contradiction.
  4. Be exact about cancellation. A cancel request records intent; it cannot stop work already executing. The handler reads the flag at checkpoints, and because a lease expires on a clock that cannot distinguish a dead worker from a slow one, a second copy may start after the cancel was recorded - so the handler re-reads the flag immediately after claiming the lease. 'cancelled' becomes terminal only when no lease is outstanding; reporting it earlier shows a client a stopped job while a worker is still writing output.

Follow-up

  • An operation has said 'running' for 40 minutes and the worker is gone. What does the client see, and which columns in job_run decide that?
  • Two tenants each submit 50 exports at once. What in this contract stops one of them delaying the other?

Design the bulk write endpoint a migration script retries blindly

Medium · batch api · partial failure · idempotency

A customer's migration script pushes 2 million resources through POST /v1/resources:batch, up to 500 items per call, and retries any call that errors or times out. Within a call some items fail validation, some collide with rows that already exist, and some succeed. Specify the request and response shape, whether a batch is atomic or per-item, how idempotency works for the call and for each item, the status code for a mixed outcome, the size and item-count limits with their error codes, and exactly what the script does after a timeout mid-batch.

Approach

  1. Choose atomicity deliberately and price it. All-or-nothing means one transaction holding locks for the whole batch on a primary already absorbing about 1.2k writes/second, which bounds batch size by lock duration, and it turns one bad row into 499 rejections the script must resend. Per-item partial success is the right default for a migration, and the contract's job is then to make a partial outcome impossible to miss.
  2. Require a client-supplied id on every item and echo it in every result. Deriving a per-item key from the array index breaks the first time the script resends a batch with the failures removed: the indices shift, previously-succeeded items acquire new keys, and they are created a second time.
  3. Key the effects at two levels. The call's Idempotency-Key covers an exact resend of the same bytes; per-item keys of (tenant_id, client_item_id) make a partially-applied batch safe to resend whole. Resending an identical batch must reproduce the same per-item results, not 500 conflicts the script has to interpret.
  4. Answer a mixed outcome with one status plus per-item detail: 200, or 207 borrowed from WebDAV if you prefer it - document whichever you pick - carrying an array of client_item_id, per-item status, and either resource_id or an error code from the same taxonomy the single-item endpoint uses. Reserve 4xx for the request as a whole: unparseable body, too many items, payload over the limit (413). Put a failed count at the top level so that even a script checking only the cheapest thing cannot conclude success while rows were dropped.

Follow-up

  • At 500 items per call, what is the wall-clock time for 2 million rows, and what pacing do you publish so the migration does not become an incident?
  • One item in every batch fails with the same code. How does the script discover that without a human reading logs?

Debugging race conditions or deadlock scenarios in Java-based systems.

Medium · Multithreading and Concurrency

Approach

  1. For a suspected deadlock, take thread dumps with jstack <pid> or jcmd <pid> Thread.print. The JVM reports 'Found one Java-level deadlock' with the owner of each monitor, and threads stuck in BLOCKED or WAITING on each other show the cycle. ThreadMXBean.findDeadlockedThreads() finds the same cycle from code.
  2. Fix a deadlock by breaking one of its conditions. Acquire locks in a single global order (for example by account id in a transfer), use tryLock with a timeout and back off, and never call external or overridable code while holding a lock.
  3. For a race, list the shared mutable state and find check-then-act or read-modify-write sequences: count++ is three steps, and get-then-put on a ConcurrentHashMap is still racy. Replace them with AtomicInteger or LongAdder, with computeIfAbsent or merge, or with a lock around the whole compound action.
  4. Separate atomicity from visibility. volatile gives visibility and ordering but does not make count++ atomic, and double-checked locking needs a volatile field. Reproduce the bug with a stress test that releases many threads together through a CountDownLatch start gate (or with jcstress), then show that the fix survives the same run.

Follow-up

  • The service hangs but the thread dump shows no deadlock cycle: how would you tell thread-pool starvation, such as tasks waiting on futures queued to the same pool, from a livelock?
  • Why is a volatile counter not enough for concurrent increments, and which class would you use under heavy contention?
  • How would you prove the fix works when the race reproduces only once in thousands of runs?

Suggested practice checklist

The plan follows the reported rounds: a baseline and the two coding clusters first (dynamic programming, then Java concurrency and debugging), then design, then the culture and leadership stories. Each day ends with something you can show.

Day 1 — Map the five rounds and set a Java baseline

  • Write the reported sequence (Technical Screening, Algorithmic Skills Assessment, System Design Discussion, Cultural Fit Evaluation, Leadership Evaluation) and mark the stage you feel least ready for
  • Solve a Coin Change 2 style problem cold in Java, with no hints, and record where you stalled
  • Write the complexity of your solution and three edge cases you should test

Deliverable: A one-page round map with the weakest stage marked, a working Java coin-combinations solution, and a note of where you stalled.

Day 2 — Dynamic programming: memoization, tables and array state

  • Solve the coin problem top-down with memoization and bottom-up with a 1D table, then explain why the loop order matters
  • Take an array problem that carries state across a scan, write the state definition first, then the code
  • Compress one 2D table to a single row and state exactly what the compression costs you

Deliverable: Three solved problems, each with a recurrence, base cases, complexity, and its list of tested edge cases.

Day 3 — Java concurrency primitives and the a-to-b printing program

  • Write the thread-safe program that prints the numbers from a to b with several threads using synchronized, wait and notifyAll inside a while loop
  • Rewrite it with ReentrantLock and Condition, then again with Semaphore, and note which is clearest
  • Run each version with 2, 3 and 8 threads and with a > b, and confirm it always terminates

Deliverable: Three working Java implementations of the printing program and a table of thread counts and ranges you ran them with.

Day 4 — Producer-consumer and custom synchronizers

  • Build a bounded buffer twice: once with ReentrantLock and two Conditions (not full, not empty), once with ArrayBlockingQueue
  • Add a clean shutdown, such as a poison pill, so consumers exit without hanging
  • Write a small custom synchronizer, such as a reusable latch or a binary gate, and explain what state its lock protects

Deliverable: Two bounded-buffer implementations with a shutdown path and one custom synchronizer, each with a note on the invariant it protects.

Day 5 — Debugging races and deadlocks

  • Write a program with a lost update on a shared counter, reproduce it by looping with many threads, and fix it two ways (a lock and an atomic type)
  • Write a two-lock deadlock, capture a thread dump, read the lock cycle, and fix it with a single lock order
  • Write a short verbal walk-through of how you would debug a hang reported in a Java service

Deliverable: A reproducible race and a reproducible deadlock, each with its fix, plus a written step-by-step debugging sequence.

Day 6 — System design discussion practice

  • Talk through one design from requirements to scaling and failure handling, using the skeleton from the System Design Discussion round
  • Explain how an in-process bounded queue maps to a queue between services

Deliverable: Two spoken design walk-throughs with a diagram each, and a list of trade-offs you chose and what each gave up.

Day 7 — Stories for the culture and leadership rounds, plus a mock

  • Run a mock with one DP problem and one concurrency problem, written in Java and explained aloud
  • Write two questions to ask the team about how engineering work is done there

Deliverable: Three written stories in the situation, action, result format, one completed mock with a list of fixes, and two questions for the team.

Culture fit and leadership stories

Candidates describe the last two rounds, Cultural Fit Evaluation and Leadership Evaluation, as covering alignment with the company and leadership and decision-making. No specific questions are reported for them, so rehearse stories that match the role as described: working with product managers and engineers, code review and testing, debugging under pressure, and owning technical design. Each story should show your own action, a trade-off and a result.

  1. Tell me about a time you debugged a hard problem in production, such as a hang or an intermittent failure. How did you narrow it down, and what did you change afterwards?
  2. Describe a technical decision you owned where you chose between two designs. What did you give up, and how did it turn out?
  3. Tell me about a disagreement with a product manager over scope or requirements. How was it resolved, and who made the final call?
  4. Describe a code review where you and the author disagreed. What did you write, and what was decided?
  5. Tell me about a time your first solution did not work and you had to iterate quickly. What did you try next, and why?
  6. Describe a time you led others, or shaped a decision, without formal authority. What was the outcome?

Estimate work you have never done and defend the range

Hard · estimation · backfills · expand-contract

You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.

Approach

  1. Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
  2. Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
  3. Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
  4. Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.

Follow-up

  • How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
  • Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?

Turn a code review disagreement into a decision

Easy · code review · optimistic concurrency · communication

A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.

Approach

  1. Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
  2. Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
  3. Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
  4. Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.

Follow-up

  • Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
  • The author says clients cannot handle a 409. How do you check whether that is true?

Argue against a design, lose, and commit anyway

Medium · disagreement · service boundaries · decision records

Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.

Approach

  1. State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
  2. Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
  3. Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
  4. Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.

Follow-up

  • What threshold on that alert would have proved you right, and did anyone ever look at it?
  • If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?