Software Engineers at Remitly build the services behind cross-border money transfers. According to the source notes, engineering teams handle transaction processing, fraud detection, regulatory compliance and ledger management across domain areas such as Core Transfer Experience, Trust Intelligence Platform, High Value Send, Decision Intelligence Platform and Receiver Products. The listed languages are Java, Go, Python, C++ and TypeScript, and the listed experience includes distributed backend services, relational databases such as PostgreSQL, and RESTful API design.
The reported questions follow that domain closely. Coding questions include a TTL cache with memory limits, finding sub-arrays of transaction records that meet a threshold, and BFS/DFS over transfer paths. API questions ask for a banking endpoint that handles concurrent deposits and withdrawals, a thread-safe structure for exchange operations, a rate limiter, and a live code review that fixes concurrency bugs. Design questions cover an international transfer platform, an event-driven ledger with exactly-once processing, real-time fraud detection, FX rate calculation and receiver notifications.
In practice, clean algorithm answers are only part of it. Across coding, API and design questions, prepare to say what happens on a retry, a timeout, a duplicate message or two concurrent writes to one balance. Candidates also report behavioral questions inside technical rounds, not only in a separate conversation, so plan to switch from code to a short structured story within the same session.
Recruiter Screen
reportedCandidates describe this as a first conversation about your background and fit for the role. The source notes that the order varies by team and location: some candidates start with a timed technical assessment, and others have a recruiter screen followed by a technical phone screen with an engineer. Use this call to find out which path you are on, whether a take-home is involved, and which domain area the team sits in. A team working on transfers, trust and fraud, or receiver products points you toward different design practice. The person on the call usually does not evaluate code, so give each project a clear shape: what was breaking, what you changed and what happened after. Describe it so a non-engineer could repeat it accurately.
What to demonstrate
- Whether your project summaries still hold up when a non-engineer paraphrases them for a hiring manager
- Whether you can tie past work to the problems this role involves, such as transaction correctness, concurrency or third-party integrations, without overstating fintech experience you do not have
- Whether you can separate what you did from what the team did on shared projects
How to prepare
- Write two-sentence versions of your top three projects with no internal codenames, and attach one measured result to each, or say plainly that it was not measured
- Prepare questions for the recruiter: which assessment format comes next, which languages are accepted, whether a take-home is involved, and which team or domain area the role supports
- Pick one story about correctness under concurrency or failure (a race, a duplicate, a retry that went wrong) and keep it ready, since it fits both this call and later behavioral questions
Technical Assessment
reportedThe source describes this stage as a technical assessment or phone screen that evaluates coding. It also says screening rounds often pair an algorithmic or API task with behavioral questions. Reported coding questions include finding sub-arrays of transaction records that meet a threshold, a cache with TTL and memory limits, BFS/DFS over dependency or transfer paths, array and string problems under memory constraints, and parsing log streams to aggregate metrics. The source describes some problems as evolutionary: a base version comes first, then more constraints are added. Finish a correct base version and keep the part most likely to change, such as the eviction rule or the threshold check, in one place so the next constraint is a small edit. When output is wrong, isolate the smallest failing input and trace it by hand before changing code.
What to demonstrate
- Whether you produce runnable code with its corner cases handled (empty input, duplicates, equal timestamps, expired entries) and state the time and space cost
- Whether you can extend a working solution when a new constraint arrives without rewriting it
- Whether a failing case is isolated and explained before any edit is made
- Whether you can move from code to a concise behavioral answer in the same session
How to prepare
- Implement an LRU cache (hash map plus doubly linked list) from an empty file, then add per-entry TTL with lazy expiry, then a memory cap, treating each step as the next constraint an interviewer might add
- Drill hash map and sliding-window problems on transaction-like arrays, plus bank questions such as O(1) insert/delete/random, isomorphic strings and weighted random pick
- Write a small parser that reads a log stream line by line, skips malformed records without crashing and aggregates counts per key, then state its memory use
- Prepare two short STAR stories you can give right after solving a problem
Virtual Onsite Loop
reportedCandidates call this the Power Day. The source reports four to five sessions covering data structures and algorithms, system architecture, a deep dive into past technical work with hiring managers, and a product alignment discussion with product managers or cross-functional leads. Behavioral questions are reported inside the technical sessions as well. In some international locations (the source mentions Poland), candidates may receive a take-home task to build an API for a banking or transaction platform, followed by an onsite code review. Treat each session as a different format. Coding rewards a finished, tested solution. Design rewards agreeing on requirements, consistency and failure modes before drawing boxes. The deep dive rewards being able to defend your own trade-offs. The product session rewards being able to explain a technical constraint in terms of what it means for the user.
What to demonstrate
- Whether a money-movement design states its consistency needs, idempotency and retry behaviour, and what happens when a downstream partner or queue fails
- Whether you can review unfamiliar concurrent code, name the specific race or bottleneck and fix it without adding locks everywhere
- Whether you can whiteboard a past project and defend the alternatives you rejected
- Whether you can work through a disagreement over priorities with a product partner and say what you would give up
How to prepare
- Run design reps on the reported shapes: transfer platform, event-driven ledger with exactly-once processing, real-time fraud scoring, FX rate calculation and cross-region receiver notifications
- Draw one past project from memory: components, data flow, the failure you handled, and two alternatives you rejected and why
- Practise a code review: take a multi-threaded snippet with a check-then-act on shared state, find the race, and explain the fix and its cost
- Prepare for the product session with a story about pushing back on a requirement for technical reasons and how you reached agreement
9 candidate reports. Individual accounts describe a particular role and hiring cycle.
Remitly Software Engineer online technical screening
After a recruiter call, I had an online technical screening split between getting to know me and solving a problem. It wasn’t a simple "here’s the pattern" LeetCode exercise, and it didn’t feel realistic to finish in a tight window without running into gaps. I spent a lot of time making sure I understood what was being asked and what passing behavior should look like. The coding round wasn’t just…
Read full experienceRemitly Software Engineer interview with a high-frequency banking prompt
My first step was an early technical prompt that was much broader than I expected for an initial stage. I had to build something like a high-frequency banking system from scratch, which meant thinking beyond one algorithm and covering the moving parts end to end. It was comprehensive, but once I broke the problem down, I could see a way through it. The next round was a live interview with behavio…
Read full experienceRemitly Frontend Engineer interview with a timed Canditech assessment
The process started with a recruiter screen, followed by a timed assessment on Canditech that lasted about an hour. The structure was rigid. Each section had its own time limit, and once I finished one, I couldn't return to it. There was no way to practice or get familiar with the platform beforehand, which made the pacing more stressful. I got thrown off immediately because I initially saw only…
Read full experienceRemitly Software Engineer interview: live coding and power-day onsite
My process started with an HR phone call. I then had a fairly structured technical interview with a live coding problem and enough time to think through the approach, followed by a separate values and discussion segment. After that, I went through what felt like a larger "power day" onsite with multiple interviewers across different roles. Some rounds focused on my resume experience. The technica…
Read full experienceRemitly Software Engineer interview: DFS screen and four-interview virtual onsite
I applied online and went straight to scheduling without a recruiter introduction. My first technical interview involved a simple DFS-style problem. I passed that round and was invited to a virtual onsite. The virtual onsite felt uneven. It included four interviews: two technical coding rounds, a manager round, and a product manager round. Some interviewers were engaged and seemed genuinely inter…
Read full experiencePracHub editorial advice for the preparation topics above.
Designing a transfer or ledger flow where a retried request or redelivered message moves money twice
Reported design and API questions ask for exactly-once processing across unreliable networks and for concurrent deposits and withdrawals. State early that the network delivers at least once, then show how duplicates are made harmless: a client-supplied idempotency key stored with a unique constraint, a ledger entry keyed by transfer id so a second insert fails, and a consumer that records processed event ids or aggregate versions. Say what the caller sees when a retry hits an in-flight or completed request.
Fixing a concurrency bug in code review by wrapping everything in one lock without naming the invariant
For the reported code-review refactor question and the thread-safe exchange structure, first name the invariant being broken, for example that a balance never goes negative or that two withdrawals must not read the same starting balance. Then point to the exact check-then-act or read-modify-write that breaks it. Choose the narrowest fix (an atomic conditional update, a per-account lock, or a version check) and say what it costs in throughput and deadlock risk. One global lock fixes the race but adds a bottleneck and leaves the reasoning unexplained.
Over-building the first version of an evolving coding problem and never getting a working base solution
The source describes coding problems that grow in complexity. Get the base case running and tested first, for example a plain cache with get/put. Then add one constraint at a time: TTL expiry, then a memory cap. Keep each rule, such as the eviction policy or the expiry check, in one function so the next constraint is a small edit. A finished base version plus one clean extension is a better result than an extensible design with nothing that runs.
Treating the technical screen and onsite coding as code-only and being unprepared when a behavioral question arrives mid-session
Candidates report behavioral questions inside coding and design sessions as well as in separate conversations. Have short STAR answers ready for the source's prompts: a decision made with incomplete information, tough code review feedback, shipping speed versus paying down tech debt, disagreement between a PM and a tech lead, and a production failure you owned. Keep each answer brief so you can return to the technical problem without losing momentum.
Describing a past project in the deep dive without explaining why it was built that way
The onsite reportedly includes a deep dive into past technical work with hiring managers, and the question bank includes whiteboarding a past project. Before the interview, draw the system from memory. Mark the component you owned, the failure or scaling limit you hit, the alternatives you rejected with the reason for each, and what you would change now. Prepare for follow-ups on numbers and on decisions made by someone else.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Given an array of transaction records, find all sub-arrays that meet s…
Given an array of transaction records, find all sub-arrays that meet specific threshold conditions.
Approach
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- How does this change if the input no longer fits in memory?
- What is the worst case, and how likely is it on real data?
Implement a custom caching mechanism with TTL (Time-to-Live) constrain…
Implement a custom caching mechanism with TTL (Time-to-Live) constraints and memory limits.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Solve array and string manipulation problems under strict runtime memo…
Solve array and string manipulation problems under strict runtime memory constraints.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Track a rolling failure rate per destination for circuit decisions
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.
- Expire idle destinations, or memory grows with every destination ever seen rather than with the live set. Hold the rings in a bounded LRU keyed on destination_id and treat a miss as no history, which is the correct default for an endpoint that has been silent for a minute.
- Keep the half-open probe out of the window arithmetic. After the circuit opens, one probe per interval decides whether to close it, and folding that single success into a window that still holds a 100 percent failure history would reopen the destination on one data point.
Worked solution 20 min
- Define the bucket struct and the advance step: take floor(finished_at_ms / 1000), compare with the ring's current second, zero min(delta, 60) buckets forward, then write into the new head.
- Trace a destination that receives 5 attempts, goes silent for 90 seconds, then receives one more, and confirm the rate is computed from one attempt rather than six.
- Compute total memory for 40,000 destinations at 60 buckets of two 4-byte counters, and state what changes if the window widens to 300 seconds.
- Write the open rule as a single predicate combining the minimum-attempt floor with the rate threshold.
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?
- How would you make the window survive a process restart, and is it worth the cost?
Write the update path that detects a concurrent edit
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
- Refuse to auto-retry the whole PUT. A retry re-reads the winner's state and reapplies an intent formed against data that no longer exists — the silent overwrite the version token was added to detect. Return the conflict; merge field-wise only if the patches are provably disjoint.
- Note that now() is the transaction timestamp in PostgreSQL, so resource.updated_at, the revision's created_at and the outbox row share one instant, which is what later makes reconciliation between the three tables unambiguous.
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?
- Every write now touches a second hot table. How do you keep the outbox insert and its partial index from becoming the write bottleneck at 1.2k writes/second?
Keep soft-deleted accounts from blocking re-registration
app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.
Approach
- Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
- Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
- For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
- State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.
- Say what a soft delete must do besides setting deleted_at: increment auth_version so existing tokens stop validating, leave resource.owner_user_id and resource_revision.actor_user_id intact, and accept that the address is retained — erasure is a different requirement answered by scrubbing the column, not by a DELETE that would break those references.
Worked solution 20 min
- Create the PostgreSQL partial unique index, insert a live row, soft delete it, and insert the same address again.
- Repeat the delete-and-reinsert cycle three times and confirm three deleted rows coexist with exactly one live row.
- Write the MySQL form with the discriminator, then deliberately reverse the polarity so live rows carry NULL, and show two live duplicates commit.
- Attempt a second live insert on both engines and map the resulting 23505 / ER_DUP_ENTRY to the 409 the handler should return.
Follow-up
- A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
- How do you honour an erasure request while resource_revision.actor_user_id still references this table?
- What changes if a user may hold membership in two tenants?
Design an international money transfer platform handling high concurre…
Design an international money transfer platform handling high concurrency and strict consistency requirements.
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Structure an event-driven ledger service that guarantees exactly-once …
Structure an event-driven ledger service that guarantees exactly-once processing across unreliable network boundaries.
Approach
- Choose a partition key and say what query it makes expensive.
- Name the read and write paths separately; they rarely have the same bottleneck.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Architect a distributed fraud detection pipeline capable of evaluating…
Architect a distributed fraud detection pipeline capable of evaluating transactions in real time.
Approach
- Name the failure you are designing for, then the recovery path.
- State the consistency you need, and where you are willing to be stale.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What breaks first when traffic grows ten times?
- How does this behave when that dependency is down for an hour?
Refactor a provided codebase during a live code review round to fix su…
Refactor a provided codebase during a live code review round to fix subtle concurrency bugs and performance bottlenecks.
Approach
- State how the contract changes without breaking existing clients.
- Define the identity of a request so a retry cannot double-apply it.
- Say who the caller is and what they do when the call fails halfway.
Follow-up
- How does a client discover it is on an old version of this contract?
- What does a partial failure look like to the caller?
Design a high-frequency banking API endpoint capable of processing con…
Design a high-frequency banking API endpoint capable of processing concurrent ledger deposits and withdrawals.
Approach
- Define the identity of a request so a retry cannot double-apply it.
- State how the contract changes without breaking existing clients.
- Say who the caller is and what they do when the call fails halfway.
Follow-up
- What happens if the caller retries after a timeout?
- How does a client discover it is on an old version of this contract?
Choose what to break when replication lag reaches forty seconds
Reads are served from two replicas: 14k requests/second, about 85% absorbed by cache, so roughly 2.1k reads/second reach the database. Writes go to the primary at 1.2k/second. A tenant's backfill drives replication lag from under 100 ms to 40 seconds and it is still climbing. Sessions that have just written are pinned to the primary. Decide, endpoint class by endpoint class, whether to serve stale, fail, or route to the primary, and justify each choice with the load it adds to the primary. Then state what you would have built beforehand.
Approach
- Establish blast radius before cause, because mitigation and diagnosis have different deadlines. The decisive arithmetic is what happens if the database reads move to the primary: 2.1k reads/second on top of 1.2k writes/second roughly triples its operation count, on the node already absorbing the backfill that caused this. Reads and writes are not equal in cost, so treat that as an argument against a blanket move rather than as a capacity model - but it is enough to rule out routing everything to the primary.
- Classify endpoints by what staleness costs, not by how important they feel. Reads whose staleness is invisible - listings, search, counters - stay on the replica and return the watermark so the client can tell. Reads that immediately follow that same session's write keep their primary pin, which is a small bounded slice of traffic rather than the whole 2.1k/second. Reads that feed a decision with a side effect - authorisation, quota, the read half of a read-modify-write - must not be stale at all, because a 40-second-old permission row is the stale-permission failure wearing a different costume; those go to the primary or fail.
- Shed instead of queueing. If the must-be-fresh class alone exceeds the primary's headroom, refuse its lowest-value slice with 503 and a retry-after. A request queued behind a saturated primary holds a connection for a client that has already given up, and the retry storm that follows is what turns degradation into an outage. Bound the connection pool per role so the read fallback cannot consume the write path's connections - that bulkhead is the single decision that determines whether writes survive the next ten minutes.
- Attack the cause in parallel, since it is the one thing that can be stopped. The backfill is the load generator. A backfill that reads replication lag as its throttle signal and pauses above a threshold would have made this a non-event, with batch sizes small enough that each batch's write volume is a fraction of what a replica can apply per second. That is most of the answer to what should have existed beforehand.
- Name the mechanism you would prefer over session pinning. Capture the write position at commit and require the read path to be at or past it: compare the primary's pg_current_wal_lsn() at commit time against the replica's pg_last_wal_replay_lsn(), and fall back to the primary only for the specific request that is ahead of the replica. Session pinning is the cheap approximation and it over-pins - every read in the window goes to the primary whether or not it needed to, which is a share of the cost being paid right now.
Worked solution 35 min
- List the endpoints in three buckets - staleness invisible, staleness visible to the writer only, staleness unsafe - and attach the share of the 2.1k reads/second each bucket carries.
- Compute the primary's operation count under each routing option and mark which options are arithmetically available.
- Write the pin rule and its window, then the shed rule: which slice, what status code, what retry-after.
- Write the backfill's throttle predicate against a measured lag value, including its pause threshold and resume condition.
Follow-up
- Lag returns to normal in nine minutes. Which mitigation do you remove first, and which one stays permanently?
- A user reports their change did not save, and the write committed. Trace the path that produces that report and name the signal that would have shown it before the report arrived.
- The replica is 40 seconds behind but otherwise healthy. Do you take it out of rotation? What does that do to the other replica's lag?
One log partition stops advancing while the others drain
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
- Fix permanently by bounding handler attempts and dead-lettering on exhaustion, so no single message can stop a partition. Then replay the dead-lettered event once the handler is fixed: it carries aggregate_id and aggregate_version, so a consumer that discards versions it has already applied can absorb the replay, and resource_revision is the fallback if the event itself is unusable.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
- What changes if the message is poison because a previous deploy wrote a payload shape the current code cannot parse?
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Map the loop and prepare your background
- Write down the three reported stages (recruiter screen, technical assessment, virtual onsite) and the questions you will ask the recruiter: assessment format, accepted language, take-home or not, and which domain area the team supports
- Write two-sentence versions of your top three projects with one measured result each, and say them aloud to someone outside engineering
- Choose your interview language from those listed (Java, Go, Python, C++, TypeScript) and list the ten standard-library calls you look up most, then practise them until you no longer need to
Deliverable: A recruiter question list, three paraphrase-proof project summaries and a language cheat list you no longer need to open.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Coding: arrays, hash maps and sliding windows
- Solve the reported transaction sub-array threshold problem and state each method's limit: a running-sum sliding window handles contiguous sums only when amounts are non-negative; a prefix-sum hash map finds exact-sum sub-arrays (negatives allowed), not threshold ones; listing every qualifying sub-array is O(n^2) output in the worst case
- Work through bank-style problems: O(1) insert/delete/random set, isomorphic strings, bijective substitution cipher and weighted random pick
- For every problem, list the corner cases before coding (empty input, duplicates, negative amounts, equal values) and test them after
Deliverable: Five solved problems, each with its complexity and a written list of the corner cases tested.
Practice prompt ↗Practice prompt ↗Practice prompt ↗03Coding: custom structures, graphs and evolving problems
- Build an LRU cache from scratch, then add TTL expiry, then a memory cap, treating each step as a new constraint arriving mid-interview
- Implement BFS and DFS over a transfer or dependency graph for reachability, and use BFS for the fewest hops between two nodes, keeping a visited set so cycles do not loop forever
- Find the cheapest currency conversion path with Bellman-Ford on -log(rate) edge weights (Dijkstra only when no weight is negative), and treat a negative cycle as an arbitrage loop to detect and report
- Work the rolling failure-rate worked exercise (drill-coding-3) and compare your memory estimate with its expected result
- Write a line-by-line log parser that skips malformed records and aggregates counts per key
Deliverable: A cache that went through three constraint stages, a BFS/DFS reachability solution, a Bellman-Ford conversion path with cycle detection and one completed worked exercise.
Practice prompt ↗Practice prompt ↗04API, concurrency and code review
- Design a deposit/withdraw ledger endpoint: request shape, idempotency key, response codes, and how two concurrent withdrawals on one account are serialised
- Write the optimistic-concurrency update from the drill-sql-1 question and compare it with the soft-delete uniqueness worked exercise (drill-sql-2)
- Take a small multi-threaded snippet with shared state, find the race, fix it with the narrowest change and explain the cost of the fix
- Sketch a token-bucket rate limiter and say how it behaves across several nodes
Deliverable: One endpoint spec with idempotency and concurrency rules, one reviewed-and-fixed concurrent snippet and one rate-limiter sketch.
Practice prompt ↗Practice prompt ↗Worked solution ↗05System design: money movement
- Design the reported international transfer platform: requirements, consistency boundaries, the ledger write path, and what happens when a payout partner times out
- Design an event-driven ledger with exactly-once processing: outbox, at-least-once delivery, and deduplication at the consumer
- Run a shorter pass on real-time fraud scoring or FX rate calculation, stating what can be stale and what cannot
- Work the replication-lag worked exercise (drill-design-4) and note which of its decisions apply to a transfer system
Deliverable: Two full designs and one shorter one, each with a written list of failure modes and recovery paths.
Practice prompt ↗Practice prompt ↗06Deep dive, product partnership and behavioral stories
- Whiteboard one past project from memory: components, what you owned, the failure you handled and two alternatives you rejected
- Prepare STAR stories for the source's prompts: decision with incomplete information, tough code review feedback, speed versus tech debt, PM and tech lead disagreement, and a production incident with its post-mortem
- Prepare a story about project slippage and how you communicated it, and one about pushing back on a product requirement
- Practise giving one story immediately after solving a coding problem, without a break
Deliverable: A project diagram you can draw from memory and seven short STAR stories.
Practice prompt ↗Practice prompt ↗07Mock virtual onsite
- Run coding, design, a past-project deep dive and a behavioral session back to back, with at least one behavioral question inside a technical session
- Work the stuck-partition debugging drill (drill-debugging-5) as a production-diagnosis warm-up and say your checks out loud in order
- After the mock, note which session suffered from the one before it and where your opening habits (restating the problem, asking for constraints) disappeared
- Reduce the week to a one-page card of rules you can recite from memory
Deliverable: Mock notes on how one session affected the next, plus a one-page card of rules you can recite.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Candidates report behavioral questions inside coding and design sessions, not only in a separate conversation. Keep each story short enough to give between technical steps. Name the people involved, what was at stake, the decision you made and the result, with a number where one exists. For stories about live systems, also say how the change could have been rolled back.
Describe a situation where you had to make a complex technical decisio…
Describe a situation where you had to make a complex technical decision with incomplete information.
Approach
- Close with what you would do differently, concretely.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Tell callers you do not own that their integration breaks
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
- State the removal criterion as a measurement with a duration attached, such as observed reads at zero across a full billing cycle, and keep the change reversible for one release after removal.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?
- After removal, what makes the change irreversible, and how long before you cross that line?
Ship under a deadline and bound the debt you chose
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.
- Report what actually happened in your real example, including the case where the trigger never fired and the debt was correctly never repaid.
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?
- Who would have overruled you if you had asked for two more days, and did you ask?
- 01
Describe a situation where you had to make a complex technical decision with incomplete information.
- 02
Tell me about a time when you received tough technical feedback during a code review and how you addressed it.
- 03
Describe a project where you had to balance delivering quickly versus taking time to refactor tech debt.
- 04
How do you approach cross-functional alignment when product managers and technical leads disagree on priorities?
- 05
Share an example of a time when a system you owned failed in production, and how you managed the incident and post-mortem.
- 06
Describe a project where you had to push back against product requirements due to technical or architectural constraints.
Is this an official Remitly interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Remitly. Rounds and questions reflect what candidates have reported, not a process Remitly has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long does the process take?
The source notes report roughly 3 to 5 weeks from the first screen to a decision, with variation between team locations and hiring cycles. Ask your recruiter for the expected timeline for your team.
PracHub interview research ↗Can I choose my programming language?
Reportedly yes. Candidates describe using their preferred language, such as Java, Go, Python, C++ or TypeScript, for live coding and take-home work. Pick the one you can write cleanly and quickly without looking up standard-library calls.
PracHub interview research ↗What does the virtual onsite cover?
The source describes the onsite, which candidates call the Power Day, as four to five sessions covering data structures and algorithms, system architecture, a deep dive into past technical work with hiring managers, and a product alignment discussion with product managers or cross-functional leads. Prepare for each as a different format, and expect behavioral questions within the technical sessions too.
PracHub Software Engineer practice ↗Is there a take-home assignment?
It depends on location. The source mentions that in some international locations, Poland for example, candidates may receive a take-home task to build an API for a banking or transaction platform, followed by an onsite code review. Ask your recruiter whether it applies to you. If it does, handle validation, errors and concurrency explicitly, because you will have to explain those choices in the review.
PracHub Software Engineer practice ↗How should system design prep differ for mid-level and senior candidates?
The source notes set a lower bar for mid-level candidates: clean service boundaries, API specs, data models and basic scaling. For senior and staff levels, they add deeper trade-offs: fault tolerance, distributed consistency, caching layers and cross-region resilience. Whatever your level, practise saying where your design needs strong consistency, which is usually the ledger, and where it can be stale.
PracHub interview research ↗How are behavioral questions handled in technical rounds?
Candidates report that behavioral questions come up within coding and design rounds, not only in a separate conversation. Prepare short STAR stories so you can answer and get back to the technical problem quickly. Prepare especially for questions about decisions under uncertainty, code review feedback, tech debt trade-offs, disagreements over priorities and production incidents.
PracHub interview research ↗Sources & methodology 3 sources ↗
Official role evidence, timestamped platform data and clearly labeled preparation advice.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
platform · Accessed 2026-09-24 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
platform · Accessed 2026-09-24 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
platform · Accessed 2026-09-24