The Software Engineer role at Airwallex is described as work on financial systems: multi-currency accounts, cross-border payments, card issuing, spend management and FX treasury. Backend engineers are described as working mainly in Java and microservices, and frontend engineers in ReactJS. The technical problems named for the role are distributed ledger precision, transaction consistency, low-latency payment processing and high availability across international jurisdictions.
The reported questions follow the same themes. Coding prompts include a reverse Polish notation calculator, a maximum over sliding-window intervals in a time series, a stream parser that handles out-of-order timestamps, and prefix matching over large datasets. Design prompts include a multi-currency wallet with double-entry bookkeeping, a transaction engine that prevents double-spending through idempotent requests, end-of-day balance reconciliation, and handling cascading failures in an asynchronous payment architecture. A third group is practical domain logic: a refund module with contradictory inputs, and clean object-oriented code that parses a payment stream into transaction summaries.
Prepare each coding answer with explicit edge-case handling, input validation and unit tests alongside a working algorithm. For money, use integer minor units or a decimal type rather than floats, put an idempotency key on every write that can be retried, and state clearly what each transaction boundary guarantees. Prepare every coding answer as if it could ship, and every design answer as if someone will audit the ledger afterwards.
Initial Screening
reportedCandidates describe the opening stage as a recruiter screen covering your background, how well your technical domain fits, and your career expectations. The role spans Java backend microservices and ReactJS frontend work across product areas such as Core Banking, Global Payments, Spend Management and FX Yield, so use this call to find out which stack and area the opening is for. Ask whether the technical stage uses live coding, a take-home followed by a code review, or both, because reports say this varies by team and location. The answers decide how you split your preparation between algorithm practice, take-home polish and system design.
What to demonstrate
- Whether your recent work matches the stack the seat needs, Java services or ReactJS interfaces, stated with concrete systems rather than a list of technologies
- Whether you can explain in a few sentences why payments or financial infrastructure work interests you, without claiming domain experience you do not have
- Whether your expectations about level, location and type of work line up with the opening
How to prepare
- Write a short summary of the two systems you know best: what they did, the stack, your part, and one number that shows scale or reliability
- Mark which of these you have built: ledgers or balance tracking, idempotent APIs, message queues, reconciliation jobs, relational schema design. Be ready to say which ones are new to you
- Ask the recruiter which product area the team sits in, whether the role is backend or frontend, and whether the technical stage uses a take-home
Technical Assessments
reportedThe technical stage is reported to combine live coding and system design, and depending on team and location it is sometimes followed by a timeboxed take-home and an in-depth code review with an architecture discussion. Reported coding prompts include a reverse Polish notation calculator, a maximum over sliding windows in a time series, a parser for a stream with variable arrival rates and out-of-order timestamps, prefix matching, and dynamic programming or graph traversal over dependencies. Reported design prompts centre on wallets, double-entry ledgers, idempotent transaction engines, reconciliation and cascading failures. Treat a correct algorithm as the minimum, not the finish line: bring explicit edge cases, clear structure and unit tests with it.
What to demonstrate
- Whether you clarify inputs and failure cases before coding, for example division by zero or too few operands in an RPN expression, and late or duplicate events in a stream
- Whether the code is structured, clearly named and backed by tests that hit the boundaries you identified
- Whether your design answers keep the ledger correct under retries, partial failures and concurrent writes, and state the consistency each path guarantees
- Whether you can defend take-home decisions in review and refactor live when a reviewer challenges one
How to prepare
- Solve the stack and window patterns until they come easily: RPN evaluation with a stack, sliding-window maximum with a monotonic deque in O(n), and largest rectangle in a histogram with a monotonic stack
- Practise one stream problem with out-of-order input: buffer events in a min-heap keyed by timestamp and release them once they fall behind a watermark set by the allowed lateness
- Design a multi-currency wallet end to end: double-entry postings, balances in integer minor units, idempotency keys on transfer requests, and a reconciliation job that compares internal entries with partner records
- After every practice problem, write three unit tests: a normal case, an empty or minimal input, and an invalid input
Behavioral Interviews
reportedCandidates report that the final stages include behavioral deep dives with engineering leadership and a Bar Raiser interview. The reported behavioral prompts cover turning an ambiguous requirement into a production system under a tight deadline, balancing code quality and test coverage against delivery speed, the hardest project you led and your specific architectural contribution, and a production bug or bottleneck you diagnosed and prevented from recurring. Pick stories where you made the decisions and can show the outcome.
What to demonstrate
- Whether your stories separate what you decided from what the team did, especially on architecture and trade-offs
- Whether you explain how you turned an unclear requirement into a concrete scope, including what you cut and why
- Whether your incident stories cover diagnosis, the fix, and the change that stopped it happening again
How to prepare
- Prepare one story for each of the four reported prompts, each with a measurable result and one trade-off you would make differently now
- For the quality-versus-speed prompt, bring a real case where you shipped with less test coverage or abstraction than you wanted, what risk you accepted, and how you contained it
- For the production-bug prompt, rebuild the timeline: first symptom, how it was detected, root cause, fix, and the test or alert added afterwards
Team Engagement
reportedThis stage is described as meeting several team members, both to show your skills and to understand how the team works. The format is not described in more detail, so ask your recruiter who you will meet and whether any part of it is technical. Use it to ask which product area and systems the team owns. Arrive with questions that help you judge the day-to-day work, and be ready to go back into technical detail if a team member asks about a project you mentioned earlier.
What to demonstrate
- Whether you can talk with engineers about their systems and connect your experience to their problems
- Whether your questions show you have thought about the work itself: on-call, how changes to money-moving code are reviewed and released, how correctness is tested
- Whether your account of your past work stays consistent and specific when a new interviewer asks about it
How to prepare
- Ask the recruiter who is joining, what they work on, and whether you should expect code or a design discussion
- Prepare questions about the team's area, such as how ledger correctness is tested, how incidents in payment flows are handled, and what shipped recently
- Re-read your answers from earlier rounds so the details of your main projects stay consistent
7 candidate reports. Individual accounts describe a particular role and hiring cycle.
Airwallex Software Engineer Interview Experience — Real-Time Robot-Chase System Design, Then a Rejection with No Feedback
It was a direct two-round company screen — coding plus system design — and if you pass, the next step is the hiring manager round. The interviewer was based in China, so we could only schedule evening slots. The team is a new one in Seattle building a productivity tool. System Design — a real-time robot-chase mini-game The problem: the player moves around on a 500×500 terrain grid, and different-…
Read full experienceAirwallex Software Engineer Interview Experience: HR discussed a second role before rejecting both
My process with Airwallex was straightforward until the HR call. The HR interviewer asked about my background and said that, based on what I shared, another role might suit the team better. We discussed that opportunity and went back and forth enough that it felt as though things were moving. Then it ended quickly. I received a rejection email saying I was not selected for either role we had disc…
Read full experienceAirwallex Software Engineer Interview Experience: CTO rejection after full loop
I completed the full Software Engineer loop, which ended with the CTO. After the recruiter conversation, I had live coding, system design, and behavioral rounds. They were standard and aligned with the role. The CTO round did not feel like a fresh evaluation. He repeated many of the questions that had come up earlier with a senior director in the behavioral discussion. Along with lower-level tech…
Read full experienceAirwallex Data Scientist Interview Experience — SQL Metrics and a KYC Conversion Case
I recently interviewed for a Data Scientist role at Airwallex and wanted to contribute my interview experience. Overall, it was SQL plus a case study. The questions themselves were not especially hard, but the pace was fairly fast, and the case placed more weight on product sense and analytical thinking. First part: SQL They gave me three tables, roughly sessions, signup, and merchant. I remember…
Read full experienceAirwallex Software Engineer Interview Experience — A Sunday Night System Design Screen Where the Interviewer Just Went Silent
This is an Australian payments company. Their dev team is mainly based in Singapore. When HR reached out, they picked a really late time slot to schedule things. Then for the phone screen, they booked me in for the system design round on Sunday night at 9pm. HR told me they use Whimsical for the whiteboard. But then it turned out the interviewer's team doesn't actually have a fixed whiteboard too…
Read full experiencePracHub editorial advice for the preparation topics above.
Representing money as floating-point values in a coding or design answer
State early that amounts are stored as integers in the currency's minor unit, or as a fixed-precision decimal, and that each amount carries its currency code. Say where rounding happens, for example once when converting currencies, and which way it rounds. In a wallet or refund question a float balance is an error the interviewer can see straight away.
Submitting a working algorithm with no validation, edge cases or tests
Treat production-ready code, edge cases and tests as part of the answer. Before coding, list the invalid inputs out loud: an empty stream, a malformed RPN token, too few operands, division by zero, duplicate or late timestamps. After coding, write tests for them. Aim for clear names and a small function per responsibility rather than one long routine.
Quietly resolving contradictory inputs in a domain-logic problem such as the reported refund module
When two inputs conflict, such as a refund above the captured amount, a currency that does not match the original payment, or a refund on a transaction already marked reversed, do not pick one silently. Name the conflict, suggest a rule (reject with a specific error, or clamp and flag it), and ask the interviewer which they prefer. Model the rules explicitly, for example with a validation step that returns typed errors, so each rule can be tested separately.
Designing a transfer or payment flow that double-charges when a request is retried
In any reported design prompt about wallets, transaction engines or distributed transactions, add an idempotency key to the write API and store it with a unique constraint in the same transaction as the ledger entries. For flows that span services, name the pattern (an outbox with at-least-once delivery and idempotent consumers, or a saga with compensating steps) and walk through what happens when the external gateway times out and the outcome is unknown.
Being unable to explain or change your own take-home code during the review
Some teams reportedly follow a take-home with a detailed code review and architecture discussion. Before the review, write down each significant decision you made, the alternative you rejected and why. Know where the weak spots are, such as missing tests, a shortcut in error handling, or a scaling limit, and bring them up before the reviewer does. Practise making a small refactor live without breaking your tests.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Solve a string manipulation problem requiring state tracking, stack ev…
Solve a string manipulation problem requiring state tracking, stack evaluation, or expression execution (e.g., building a reverse Polish notation calculator).
Approach
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
- 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?
Given an input stream of data points, implement an optimal parser and …
Given an input stream of data points, implement an optimal parser and processing logic that handles variable arrival rates and out-of-order timestamps.
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
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?
Write a program to calculate the maximum speed achievable over sliding…
Write a program to calculate the maximum speed achievable over sliding window intervals in a numerical time-series stream.
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Name the brute-force solution and its complexity before improving on it.
- Walk one small example through your approach before writing the whole thing.
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?
Collapse a redelivered event batch into per-aggregate high-water marks
You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.
Approach
- One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
- Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
- Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
- If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.
- Reject sorting the batch by (aggregate_id, version) as the default. It is O(n log n) and buys nothing, because max is associative and commutative and needs no ordering; sorting earns its cost only when the downstream consumer must receive the events in order rather than a per-aggregate winner.
- Separate the two mechanisms out loud: in-batch deduplication does not make the consumer idempotent, because the same event redelivered tomorrow arrives in a different batch entirely. The projection write itself still has to be keyed on (aggregate_id, version).
Worked solution 20 min
- Write the pass: look up last_applied_version, skip if the event's version is not greater, otherwise upsert into the keep-map only when the incoming version exceeds the version already held, incrementing the discard counter on every skip.
- Hand-trace one aggregate whose events arrive as v5, v3, v4, v5 with last_applied_version = 2, and confirm the output holds v5 once while the counter reads 3.
- Compute the table footprint for 2,000,000 entries at 12 bytes of payload and a 0.7 load factor, then state the multiplier for a runtime that boxes keys and values.
- Add the hash-partitioning fallback and say in one sentence why the per-partition results need no cross-partition merge logic.
Follow-up
- The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
- How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?
- Two events for one aggregate carry the same version with different payloads. Which one is wrong, and how would you find out?
Find version gaps and relay lag with window functions
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
- Interpret rather than report: no gaps plus a normal p95 of published_at - created_at points at the consumer; gaps or a fat lag tail point at the relay; rows still 'pending' with attempts > 0 point at neither, because they never left the database.
- Be explicit that the partial index on (created_at, event_id) WHERE status = 'pending' does not serve any of these — they read published rows. Name the index a recurring monitor would need, and say why a query run twice a year may not deserve one.
Worked solution 30 min
- Write the three queries against seven days of data and confirm each returns without error.
- In a scratch copy, delete one middle event for a single aggregate and confirm the gap query names that aggregate and the versions either side.
- Run a running total over ungrouped rows ordered by date_trunc('second', created_at), once with the default frame and once with ROWS, and record where the two series diverge.
- Compare the DISTINCT ON and row_number() plans on the same data and record rows-read for each.
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?
- The consumer claims it never received event 4,812,006. What do you look at, in what order?
Stop tag and share joins from fanning out a page
resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.
Approach
- Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
- Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
- Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
- Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.
- Attach the tags for display after the page has been cut — LEFT JOIN LATERAL (SELECT array_agg(rt.tag_id) FROM resource_tag rt WHERE rt.resource_id = p.resource_id) ON TRUE over the 50 returned rows. Aggregate over the page, never over the tenant.
- Index both directions and say which query each serves: PK (resource_id, tag_id) serves the lateral lookup, (tag_id, resource_id) serves the EXISTS probe by tag, and resource_share needs (shared_with_user_id, resource_id) for the same reason. An index covering one direction only leaves the other as a scan.
Follow-up
- The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
- A resource can be shared with the same user twice under different permissions. Does your count change, and should it?
- Where does the correct total come from when the tenant holds 4M resources and the header must not cost 200 ms?
How would you architect a distributed, microservices-based transaction…
How would you architect a distributed, microservices-based transaction engine to prevent double-spending and ensure idempotent request execution?
Approach
- 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.
- Choose a partition key and say what query it makes expensive.
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?
Describe your strategy for monitoring, troubleshooting, and resolving …
Describe your strategy for monitoring, troubleshooting, and resolving cascading failures within an asynchronous event-driven payment architecture.
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Design a multi-currency digital wallet system supporting instantaneous…
Design a multi-currency digital wallet system supporting instantaneous balance checks, ledger double-entry bookkeeping, and global transfers.
Approach
- Name the failure you are designing for, then the recovery path.
- Choose a partition key and say what query it makes expensive.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- How does this behave when that dependency is down for an hour?
- What would you drop to keep the system up under load?
Given a raw payment stream, structure clean object-oriented code using…
Given a raw payment stream, structure clean object-oriented code using proper abstraction and design patterns to parse and output transaction summaries.
Approach
- Clarify what is being asked and what a complete answer contains.
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Evolve the resource contract without breaking integrations you cannot upgrade
GET /v1/resources/{id} returns status from a four-value enum ('draft','active','archived','deleted'), a numeric version, and the body inline. Consumers are a browser app you deploy and roughly 300 server-side integrations, some untouched for two years, that switch exhaustively on status and parse ids as JSON numbers. You must add a 'pending_review' status, move bodies over 256 KB to a body_ref pointer, and expose per-field change history from resource_revision. Specify the compatibility policy, the wire changes, how both generations are served, and the evidence that lets you remove the old shape.
Approach
- Write the policy first and date it: fields are added, never retyped or repurposed; consumers ignore unknown fields; an unknown enum value maps to a documented fallback; nothing is removed until telemetry shows no caller reads it. Then say the uncomfortable part out loud - v1 shipped without the unknown-value rule, so 300 running integrations have no fallback, and no server change can install one into code that is already deployed.
- That single fact forces per-request negotiation rather than a server-side default. Keep one internal model and select a serialiser from an explicit version in the request, and default a caller that sends nothing to the oldest supported version. Defaulting to the newest is the change that breaks every integration that never asked for anything, on the day you ship.
- Downgrade 'pending_review' for old callers to the nearest state they already handle, 'draft', and state the loss explicitly: those integrations cannot see review state and will treat the resource as editable. If that is unacceptable for one integration, the remedy is moving it to the new version, not a cleverer projection - there is no mapping that invents a state the client has no code for.
- Make the body change additive. Old callers keep
bodyinline; the new shape addsbody_refand a size field, and resources over the limit are served to old callers by resolving the pointer server-side or by refusing with a documented code, chosen once and published. Never repurposebodyto carry the pointer: a client that renders it shows a storage key to a user, and that failure is silent, where a missing field would have been loud. While you are here, serialise BIGINT ids as strings in the new shape - a browser parsing JSON numbers gets IEEE-754 doubles, exact only to 2^53 - and treat that as its own breaking change requiring the same negotiation, not a quiet fix. - Add change history as a separate sub-resource, GET /v1/resources/{id}/revisions, keyset-paginated over (resource_id, version) rather than as an array inside the resource. A field added to a hot response is paid for by every caller including those that never read it, and an unbounded array inside a cached object destroys the size assumptions the cache was configured with.
- Retire on evidence rather than on a date alone: count requests per negotiated version per credential, publish a Sunset header (RFC 8594) with the removal date and a link to the migration, contact the credentials still on the old version, then answer 410 Gone once it is removed. Keep each version's serialiser under snapshot tests so a refactor cannot change v1's bytes by accident.
Worked solution 40 min
- Write and date the compatibility policy as five rules, then mark which of them v1 callers cannot honour.
- Choose the negotiation mechanism and the default for an unversioned request, and justify the default in one sentence.
- Write the downgrade table: new state to old state, new body shape to old body shape, and what is lost in each direction.
- Specify the revisions sub-resource: its URL, its cursor, and why it is not a field on the resource.
- Write the sunset plan: the per-version per-credential metric, the header, the lead time, and the terminal status code.
Follow-up
- An old integration submits a status transition while the resource is really in 'pending_review'. What does the write path accept, and what does it reject?
- Two years on you want to delete the v1 serialiser. What evidence makes that safe, and who must be contacted before it happens?
- How would you test against a two-year-old integration rather than against today's source?
Exports duplicate a row range about once a week
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
- 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.
- 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.
- 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.
- 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.
- Accept that a lease can still expire under a slow worker, because a lease cannot distinguish slow from dead, and fence the work. Carry a monotonically increasing lease generation and make every write the handler performs conditional on still holding it, as UPDATE ... WHERE job_run_id = $1 AND lease_owner = $2 AND lease_generation = $3, so a displaced worker's writes affect zero rows and it aborts instead of duplicating.
- Make the handler's writes idempotent independently of all that: give each exported chunk a natural key of (job_run_id, batch_start) with a unique constraint so a second copy conflicts rather than appends, move the heartbeat off the handler's thread, and increment attempt on takeover so the event is visible in a metric.
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?
- What breaks if you simply raise the lease to five minutes?
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Screening prep and scoping the loop
- Decide whether you are targeting Java backend or ReactJS frontend work and rewrite your two strongest projects in those terms: system, stack, your part, and one measurable result
- Mark each area as done, adjacent or new: ledgers, idempotent APIs, message queues, reconciliation, SQL schema design and query tuning
- Write your recruiter questions: product area, backend or frontend, whether there is a take-home, and who you will meet in the team stage
- Group the reported questions in this guide into coding, design, domain logic and behavioral, and rank each group by how ready you feel
Deliverable: A one-page screening brief with two project summaries, a skills map and your recruiter questions.
Practice prompt ↗Practice prompt ↗Practice prompt ↗Worked solution ↗02Coding: stacks, strings and search
- Implement a reverse Polish notation calculator with a stack, and handle bad tokens, too few operands, leftover operands and division by zero with clear errors
- Solve largest rectangle in a histogram with a monotonic stack in O(n), then largest square of ones with the DP rule: cell = min(top, left, diagonal) + 1
- Warm up with two sum (hash map), binary search for the first index that meets a condition, and cycle detection with Floyd's two pointers
- Write three unit tests for each problem: normal, minimal or empty, and invalid input
Deliverable: Five solved problems, each with its complexity stated and a small test suite that covers invalid input.
Practice prompt ↗Practice prompt ↗03Coding: sliding windows, streams and merges
- Solve the maximum over a sliding window of a time series with a monotonic deque in O(n), then change it to time-based windows where timestamps are unevenly spaced
- Build a stream processor for out-of-order events: a min-heap keyed by timestamp, a watermark of max-seen minus allowed lateness, and a rule for events that arrive after the watermark
- Merge many paginated sorted sources into one deduplicated stream with a k-way heap merge that fetches pages lazily, and state the O(N log k) cost
- Work through the existing worked exercise on collapsing a redelivered event batch (drill-coding-3) and compare max-wins with last-wins
Deliverable: Three stream-style solutions with tests for late, duplicate and out-of-order input.
Practice prompt ↗Practice prompt ↗04Domain logic and clean object-oriented code
- Implement a refund module: list the contradictory inputs (over-refund, currency mismatch, already reversed), choose a rule for each, and return typed validation errors
- Build a payment-stream parser that outputs transaction summaries, with separate classes for parsing, validation and aggregation, and amounts in integer minor units
- Write unit, integration and scenario tests that would catch data loss in a ledger: posting totals balance to zero, replayed input produces the same state
- Review your code for naming, function size and separation of concerns as if it were a take-home submission
Deliverable: Two small modules with tests and a short README explaining your design decisions, as you would for a take-home.
Practice prompt ↗Practice prompt ↗Worked solution ↗05System design: wallets, ledgers and idempotency
- Design a multi-currency wallet: double-entry postings, how balances are derived or cached, FX conversion and rounding, and the API with idempotency keys
- Design a transaction engine that prevents double-spending: unique idempotency keys, row locks or conditional updates on balances, and what happens when a gateway call times out
- Cover distributed transactions across services with an outbox and idempotent consumers or a saga with compensations, then work through the existing SQL exercise on outbox version gaps (drill-sql-1)
- Add end-of-day reconciliation that compares internal entries with partner records and sends mismatches to a review queue
Deliverable: Two design write-ups, each with requirements, API, schema, the consistency guarantee per path, and failure handling.
Practice prompt ↗Practice prompt ↗06Reliability, debugging and APIs
- Plan how you would handle cascading failures in an async payment pipeline: timeouts, bounded retries with jitter, circuit breakers, dead-letter queues, and the metrics that show backlog
- Practise debugging out loud from a stack trace: reproduce, narrow down, find the root cause, fix, add a regression test. Then work through the lease-takeover debugging drill (drill-debugging-5)
- Review RESTful API design, synchronous versus asynchronous processing, load balancing, and database query tuning with indexes and query plans
- Work through the existing API evolution exercise (drill-design-4) and note how you would version a payments API that external integrations depend on
Deliverable: A one-page incident playbook for an event-driven payment flow and notes on one debugging walkthrough.
Practice prompt ↗Practice prompt ↗07Behavioral, team stage and a full mock
- Rehearse stories for the four reported behavioral prompts: an ambiguous requirement, quality versus speed, your hardest project, and a production bug you prevented from recurring
- Prepare answers on leading through startup ambiguity and on how you use AI tools at work and when you would hand a task to an agent
- Write team-stage questions about correctness testing, incident handling and recent launches in the team's product area
- Run one mock with a coding problem and a design problem back to back, then note any gaps and revisit them
Deliverable: Six rehearsed stories with results and trade-offs, a list of team questions, and notes from the mock.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
The final stages reportedly include behavioral deep dives with engineering leadership and a Bar Raiser interview. Choose stories where you made the call, can name the trade-off, and can show a measurable result. Expect follow-ups on what you personally did as opposed to what the team did, and on what you changed afterwards.
Describe a time you turned an ambiguous product requirement into a hig…
Describe a time you turned an ambiguous product requirement into a high-impact, production-ready system under tight deadlines.
Approach
- Close with what you would do differently, concretely.
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
How do you balance code quality, test coverage, and architectural abst…
How do you balance code quality, test coverage, and architectural abstraction with the speed required in a fast-paced fintech environment?
Approach
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Argue against a design, lose, and commit anyway
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
- 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.
- 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.
- 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.
- 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.
- Report the outcome without editing it. If the design held and your predicted mechanism never fired, say so and say what you had mis-weighted, which is more persuasive than a vindication story.
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?
- How did you behave toward the design once it shipped and started failing in a different way than you predicted?
- 01
Describe a time you turned an ambiguous product requirement into a high-impact, production-ready system under tight deadlines.
- 02
How do you balance code quality, test coverage, and architectural abstraction with the speed required in a fast-paced fintech environment?
- 03
Walk me through the most technically challenging project you have led, focusing on your specific architectural contributions and design trade-offs.
- 04
Describe an instance where you identified a critical production bug or system bottleneck; how did you diagnose, resolve, and prevent it from recurring?
- 05
Tell me about a time you had to lead through startup ambiguity: requirements were incomplete and the path forward was unclear.
- 06
How do you use AI tools at work, and when would you delegate a task to an agent?
Is this an official Airwallex interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Airwallex. Rounds and questions reflect what candidates have reported, not a process Airwallex has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How difficult are the technical interviews at Airwallex?
The reported questions mix algorithm problems, such as an RPN calculator, a sliding-window maximum and an out-of-order stream parser, with wallet, ledger and payment design and domain-logic exercises such as a refund module with contradictory inputs. Prepare to deliver working code with explicit error handling and tests, and to explain the design trade-offs behind it.
PracHub interview research ↗What programming language should I use for the technical assessments?
Candidates can reportedly pick their language (Java, Python or C++, for example) for general algorithm rounds, while role-specific rounds are reported to test the core stack: Java for backend engineers and ReactJS for frontend engineers. Ask your recruiter which applies to your opening, and practise in the language you will use on the day.
PracHub interview research ↗Does Airwallex focus heavily on fintech domain experience during interviews?
Banking or payments experience is described as helpful but not required. Several reported design and domain-logic questions involve financial scenarios such as refunds, wallets and double-spending, so learn double-entry bookkeeping, idempotency keys, integer minor-unit amounts and reconciliation well enough to use them without prompting.
PracHub interview research ↗How long does the complete recruitment process take from start to offer?
Candidates report four rounds over roughly three to five weeks, and other accounts put it at up to six weeks, depending on candidate availability, office location and team. Ask your recruiter for the expected timeline once the screen is scheduled.
PracHub interview research ↗Will there be a take-home assignment?
Possibly. The technical stage is reported to combine live coding and system design, and depending on team and location it is sometimes followed by a timeboxed take-home and an in-depth code review with an architecture discussion. Ask during the screen. If you get one, include tests and a short note on your design decisions, and be ready to defend and change the code in the review.
PracHub Software Engineer practice ↗What is the Bar Raiser interview mentioned for the final stages?
The final stages reportedly include behavioral deep dives with engineering leadership and a Bar Raiser interview. Its format is not described, so ask your recruiter. Prepare specific ownership and technical-decision stories that hold up under several rounds of follow-up questions.
PracHub Software Engineer practice ↗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