Skip to content
QSWEQB

Banking and financial services fundamentals

What a bank looks like from the inside: the append-only ledger and its four balances, the rails money moves over and what finality costs, cards and disputes, credit decisioning, and the controls wrapped around all of it. Sixty items, fourteen worked through with ledger rows, arithmetic or a diagram.

60 questions

Go deeper on Banking & Financial Services

The core entities

What is a bank, from a systems point of view?

A ledger, a set of connections to external payment rails, and a financial-crime apparatus wrapped around both. Everything customer-facing is a read model over that core. The framing matters because it locates the hard engineering outside your own service boundary: the ledger has to balance to the minor unit and stay defensible for years, the rails are third-party systems with operating hours, cut-offs and finality rules you cannot retry your way past, and the compliance layer can freeze a transaction your product logic has already accepted. A candidate who describes a bank as CRUD over an accounts table has missed all three, and will design a payment API that assumes the money movement is a function call which either returns or throws. The interesting constraints are almost never in the application tier.

Why are the customer and the account modelled separately?

Because the relationship is many-to-many in both directions and it changes over time. One customer holds several accounts, one account has several parties — a joint account, a company account with signatories, a trust with beneficiaries — and a person can be a beneficial owner without being a party to the account at all. Collapsing them makes the joint account unrepresentable and leaves the due-diligence record with nothing to hang on, since diligence is performed on a person or a legal entity rather than on an account number. There is a lifecycle consequence too: closing an account must not remove the customer, because the identity evidence carries its own retention obligation and other products may still be open. Interviewers ask because the wrong answer surfaces in the schema immediately.

What is a product, and what does it do to your schema?

A product is the set of terms an account is opened under — interest rate, fee schedule, overdraft eligibility, currency, statement cycle, early-withdrawal penalty — held separately so that a hundred thousand accounts share one definition rather than each carrying a copy. Modelling it properly is what stops a rate change becoming a mass update with no history. The part candidates omit is the version: an account is opened under a product version, and every version must be retained, because "what rate was this customer promised in 2021" is a question you will be asked by the customer, by a regulator and possibly by a court. So product configuration is effective-dated reference data with an audit trail, not a settings table somebody edits in place.

What does KYC actually require, and what does it produce?

Verified identity for every party, evidence of that verification, a risk rating, and a record you can defend years later. In practice that means identity and address documents checked against an authoritative source or a bureau, liveness or biometric matching on a remote channel, beneficial-ownership resolution for a corporate customer, sanctions and politically-exposed-person screening, and source-of-funds enquiry for the higher-risk cases. What it produces is what matters to engineering: an immutable case file recording what was checked, by which provider, with what result, at what time, and who decided. That is why onboarding cannot be a stateless form. It is an evidence-generating workflow with a persisted state machine, idempotent retries against flaky third parties, and manual review as a designed path rather than an error branch.

Show me the customer, account and product model and where it goes wrong.

Four tables and one join table carry most of retail banking, and the join table is the one that gets flattened away.

party             a person or legal entity. Diligence attaches here.
  party_id, party_type, legal_name, date_of_birth, country,
  risk_rating, kyc_case_id

account_party     the relationship. Many to many, and effective dated.
  account_id, party_id,
  role         holder | signatory | beneficial_owner | mandate_delegate
  valid_from, valid_to

account           the container postings are made against.
  account_id, product_version_id, currency, status,
  opened_at, closed_at            -- closed, never deleted

product_version   the terms. New row per change, never edited in place.
  product_version_id, product_id, version, interest_rate,
  fee_schedule_id, effective_from, effective_to

posting           the ledger. Append only. One row per side of a transaction.
  posting_id, account_id, dr_cr, amount_minor, currency,
  transaction_id, value_date, booked_at

The role column is the whole point of the join table. A signatory can instruct payments but is not an owner of the funds, a beneficial owner is subject to diligence but cannot instruct anything, and a delegate holds a time-boxed mandate that must expire. Systems that store a single customer_id on the account cannot express any of that, and the workaround is invariably a second hidden account per extra party, which then breaks every balance and every statement.

Effective dating on account_party is what lets you answer who was authorised on the day a disputed payment was made. Without valid_from and valid_to you know the current mandate and nothing else, and the dispute you are being asked about is eight months old.

currency sits on the account and not on the product because a multi-currency product issues one account per currency, and a posting must never mix units. Storing amounts as integer minor units rather than a decimal or a float is the other non-negotiable, since binary floating point cannot represent a tenth of a penny and a rounding error in a ledger is a reconciliation break somebody has to investigate.

The closed_at column instead of a delete is the pattern repeated throughout banking. Nothing is removed, because the transaction history, the diligence evidence and the tax records all outlive the relationship, and an erasure request against a closed account is answered by a legal retention exemption rather than a DELETE.

What is AML transaction monitoring, in engineering terms?

A stream of every money movement evaluated against rules and models that raise alerts for human investigation, plus the case-management system those alerts land in. The rules are recognisable — structuring just under a reporting threshold, rapid movement through an account that keeps no balance, unexpected geography, sudden change from an established pattern, layering through many small counterparties — and each needs history and context rather than the current transaction alone. The engineering shape is therefore a feature store keyed by party with rolling windows, a rules engine whose versions are retained so a decision can be reproduced, and a queue with investigator assignment, decision rationale, escalation and permanent history. Two constraints define the work: you may not tip off the subject, and false positives dominate volume, so the real problem is raising precision without dropping a true case.

Show me a sanctions screening hit and the false-positive arithmetic.

Screening is the clearest example in banking of a control whose costs are operational rather than computational.

Outbound payments screened, one month: 1,000,000
Fuzzy name matching, threshold tuned to catch transliteration variants

  alerts raised                     4,200     0.42% of volume
  confirmed true matches                3
  false positives                   4,197     99.93% of alerts
  analyst time per alert              4 min
  review effort                     280 hours ~2 full-time analysts

Raise the threshold to cut the queue:

  alerts raised                     1,200
  false positives                   1,198
  true matches missed                   1     <- a sanctions breach
  effort saved                      200 hours per month
  cost of the miss                  fine, licence risk, personal liability

The asymmetry is the answer. Two hundred analyst hours a month is a real cost with a known price, and a single missed designated party is an unbounded one, so the threshold cannot be tuned by optimising a single accuracy metric. Anyone who proposes trading recall for precision here has not understood what the control is for, and saying so unprompted is worth more than knowing the matching algorithm.

The engineering answer is to reduce false positives without moving the threshold. Resolved matches are whitelisted per party and per list version, so the same supplier is not re-alerted every month, and the whitelist is invalidated whenever the list changes because the entry may now be a genuine hit. Secondary attributes discriminate where names cannot: date of birth, country, and a structured address turn "Ali Hassan" from a match into a mismatch, which is one of the strongest practical arguments for ISO 20022's structured party data.

The reproducibility requirement is the one candidates miss. A screening decision must be replayable years later, so you retain the list version, the matching configuration, the score, the decision and the analyst, not merely the outcome. A regulator reviewing a payment from 2024 will ask why it was released, and "the current engine says it is fine" is not an answer about 2024.

Note also where screening sits in the flow. It runs before submission to the rail, which makes it a synchronous dependency on the payment path with a human in the loop, and that is exactly how a payment misses a cut-off — which is a different worked item and the same root cause.

What is perpetual KYC, and why is it a data problem?

The move from re-verifying customers on a fixed cycle — high risk yearly, low risk every five years — to continuously re-evaluating risk as evidence changes. It exists because periodic review is simultaneously wasteful and late: most files are reviewed when nothing has changed, and the file that mattered went stale eleven months before its review date. Engineering-wise it turns onboarding data into a living entity with triggers: an address change, a new director, a screening list update, a change in transaction behaviour, an adverse-media hit, an expiring document. Each trigger re-scores the party and may open a review case. What makes it hard is that the evidence lives in a dozen systems and the triggers arrive as events, so you need a party-level event history and a risk model that can be recomputed and explained, not a last_reviewed date.

The ledger

Why is a banking ledger append-only?

Because the history is the product. An update destroys the evidence that a regulator, an auditor, a court or a customer will later ask you to produce, and "the balance was wrong and we corrected the row" is indistinguishable from fraud once the row is gone. So a mistake is fixed by posting a reversing entry and then the correct one, leaving three visible facts rather than one invented one. The consequences run through the whole design: the ledger table only ever receives inserts, so it grows without bound and needs partitioning by date rather than pruning; corrections are a first-class product feature with their own authorisation; and any downstream projection can be rebuilt by replaying postings, which is what lets you prove a reported balance rather than assert it.

What do debit and credit mean in accounting, and why does a customer use them backwards?

In double-entry bookkeeping a debit increases an asset or reduces a liability, and a credit does the reverse. A customer deposit is a liability of the bank — money it owes you — so paying money in credits your account and taking money out debits it. That is why a customer saying "credit my account" means "give me money", while the same word in the general ledger means the bank's obligation grew. The confusion is not academic: it produces sign errors in reporting, in statement rendering and in reconciliation, and it is the reason interviewers ask. The habit that avoids it is never to store a signed amount. Store an unsigned amount plus an explicit DR or CR, and derive the sign per account type at read time, so the convention is applied in exactly one place.

Show me one transfer as double-entry rows, with the balance derived.

An internal transfer of 250.00 between two customers of the same bank, and what the rows have to look like for the ledger to be defensible.

transaction 9f31c8 -- transfer GBP 250.00, customer A to customer B

posting_id  account          dr_cr  amount_minor  transaction_id  booked_at
----------  ---------------  -----  ------------  --------------  -----------------
p-1         cust-A-current   DR           25000   9f31c8          2026-07-14T10:02:11Z
p-2         cust-B-current   CR           25000   9f31c8          2026-07-14T10:02:11Z

  sum(DR) = 25000   sum(CR) = 25000   difference = 0   <- the invariant

Balance is not a column:

  SELECT sum(CASE WHEN dr_cr = 'CR' THEN amount_minor
                  ELSE -amount_minor END) AS balance_minor
  FROM   posting
  WHERE  account = 'cust-B-current'
    AND  value_date <= '2026-07-14';

A wrong amount is fixed by posting, not by updating:

p-3         cust-A-current   CR           25000   9f31c8-rev      reverses p-1
p-4         cust-B-current   DR           25000   9f31c8-rev      reverses p-2
p-5         cust-A-current   DR           27500   9f31c8-cor      the correct 275.00
p-6         cust-B-current   CR           27500   9f31c8-cor      the correct 275.00

The invariant is the reason the model exists. Every transaction writes rows summing to zero, so a single-sided write is detectable by a query that runs across the whole ledger, and a corrupted transfer cannot silently create or destroy money. That check should run continuously rather than at month end, because the value of the invariant is that it fails loudly and immediately.

Deriving the balance rather than storing it removes the classic lost-update bug, where two concurrent transfers each read 1,000, each write their own result, and one movement disappears with no error anywhere. The postings are independent inserts, so concurrency is handled by the database rather than by application locking.

The cost is read performance, and pretending otherwise is the weak version of this answer. Summing ten years of postings for every balance enquiry does not survive contact with production, so real ledgers keep a periodic checkpoint — a balance_snapshot row per account per day — and derive the current balance as the snapshot plus the postings since. That is a materialised aggregate, not a replacement for the postings, and it can be rebuilt at any time from the rows.

The correction rows show the other half of the model. Six postings now exist for one intended transfer, the statement shows all six, and that is the correct outcome: the customer can see what happened and so can an auditor. The transaction_id linking a reversal to its original is what lets the statement present them as a correction rather than as three unexplained movements.

Which four balances does an account have, and how do they diverge over a day?

Ledger, available, cleared and pending are different numbers for good reasons, and the app shows only one of them.

Account 40021 -- no overdraft. Cheque deposits clear at T+2.

  ledger    = sum of posted entries
  available = ledger - holds - uncleared credits + agreed overdraft
  cleared   = ledger - uncleared credits            funds with finality
  pending   = authorisations held but not yet posted

time   event                                 ledger  available  cleared  pending
-----  ------------------------------------  ------  ---------  -------  -------
08:00  start of day                         1200.00    1200.00  1200.00     0.00
09:14  card authorisation 4.20, hold only    1200.00    1195.80  1200.00     4.20
10:02  faster payment in 300.00, final       1500.00    1495.80  1500.00     4.20
11:30  cheque deposit 250.00, uncleared      1750.00    1495.80  1500.00     4.20
14:05  standing order out 900.00, final       850.00     595.80   600.00     4.20
16:40  card capture 4.20 posts, hold ends     845.80     595.80   595.80     0.00

At 16:40 the ledger says 845.80 and the customer can spend 595.80, because 250.00 of the ledger balance is a cheque that has not cleared. A naive SELECT balance returns 845.80 and authorises a payment the account cannot fund, which is a real loss rather than a display bug — the bank has released final funds against a cheque that may still bounce.

The 11:30 row is where the four numbers separate most usefully. The deposit is in the ledger because the bank has recorded the instrument, it is not in the cleared balance because the funds are not yet final, and it is not in the available balance because this bank chooses not to advance credit against it. Another bank might make the funds available immediately and carry the risk, which is a commercial decision expressed as one term in a formula.

The design consequence is that "balance" is never a column name. Each of the four is a query with a definition somebody signed, and the API must name which one it returns, because a statement wants the ledger balance at a value date, a payment authorisation wants the available balance now, and a regulatory report wants the cleared balance at close of business. Systems that expose one number end up with four different implementations of it in four clients.

The last detail is the overdraft term. An agreed limit increases availability without changing the ledger, so an available balance can exceed the ledger balance and can be positive while the ledger is negative. Anyone treating available as "ledger minus a bit" gets overdrafts wrong in both directions.

What is a hold, and what is it not?

A hold is a reduction in the available balance with no ledger posting behind it: the money has not moved, but it is reserved. Card authorisations create them, so do pending outbound transfers, disputed amounts and legal freezing orders. What a hold is not is a transaction, and that distinction has consequences. It does not appear on a statement as a movement, it does not affect the cleared balance, it has an expiry set by scheme or internal rules, and it must be released explicitly when the underlying event resolves. A hold that is never released is money the customer cannot spend and cannot see explained, which is one of the most common retail-banking complaints, and it is almost always an integration that received a capture it failed to match back to the reservation.

Show me an authorisation hold expiring and what it does to the balance.

The hotel pre-authorisation is the canonical case, and its failure mode is a capture arriving after the reservation has gone.

Account 40021, lodging pre-authorisation, scheme auth lifetime 7 days

The normal path
  day 0  10:12  auth 400.00 approved, auth code 7731
                ledger 1850.00   available 1450.00   holds 400.00
  day 3  09:40  hotel captures 268.40 against auth 7731
                posting DR 268.40, hold 7731 released
                ledger 1581.60   available 1581.60   holds 0.00

The failure path -- no capture arrives
  day 0  10:12  auth 400.00 approved, auth code 7731
                ledger 1850.00   available 1450.00   holds 400.00
  day 7  10:12  hold expires by scheme rule, nothing to match it to
                ledger 1850.00   available 1850.00   holds 0.00
  day 8  11:05  customer spends 300.00 against the restored availability
                ledger 1550.00   available 1550.00   holds 0.00
  day 9  14:20  late capture for 268.40 arrives citing auth 7731
                posted regardless -- the issuer is obliged to pay
                ledger 1281.60   available 1281.60   holds 0.00

The expiry on day 7 is correct behaviour, not a bug. A hold with no matching capture cannot be held forever, so the schemes define a lifetime by merchant category — short for retail, long for lodging and car hire — and the issuer must release at that point or the customer's money is trapped indefinitely.

Day 9 is where the engineering lives. The clearing message arrives after the reservation expired, and the issuer is still liable to pay it under the scheme rules, so it posts against an account whose availability was already spent. The customer experiences a debit they thought had gone away, and if the balance was tighter, an unarranged overdraft with a fee attached.

The mitigations are all about matching rather than about preventing the posting. Captures are matched on the authorisation code plus the acquirer reference rather than on the amount, since a partial capture is normal and an amount match would silently double-post the tolerance cases. Unmatched captures go to an exceptions queue rather than posting blind. And the customer-facing fix is to surface expired authorisations in the app as "released, may still be charged", because the complaint is caused by silence rather than by the debit.

Two further details worth naming: a partial capture must release the unused portion of the hold in the same operation, and an authorisation reversal from the merchant — the cancelled booking — must be handled as its own message type, because if you ignore reversals the hold sits there until expiry and the customer is out of pocket for a week for a hotel they never stayed in.

What is the difference between booking date and value date?

The booking date is when the entry was recorded in the ledger; the value date is the date it counts from for interest and for finality. They differ routinely — a payment submitted after a cut-off is booked today and valued tomorrow, a back-valued correction is booked in July and valued in June — and the difference is money, because interest accrues on value date. That has two design consequences. A balance query is meaningless without a date basis, so the API must say whether it means the ledger balance as booked or as valued. And a back-valued posting retroactively changes interest already accrued, which means interest calculation has to be re-runnable for a prior period rather than a job that runs once and forgets.

Show me a cut-off time changing a value date.

Cut-offs cause more corporate-banking complaints than outages do, and the cause is usually a control in the payment path rather than a slow system.

Rail: domestic RTGS. Cut-off 16:00. Value date = booking date if submitted before.

PAY-4471   GBP 2,400,000.00   submitted Friday 2026-07-17 by the corporate portal

  15:58:12  accepted by our API                        state ACCEPTED
  15:58:13  sanctions screening queued
  15:59:40  fuzzy match on the beneficiary name        state HELD
  16:04:55  analyst clears it as a false positive      state RELEASED
  16:04:56  rail submission rejected: window closed
  16:05:00  requeued for the next business day

  booked  2026-07-17 (Friday)      value  2026-07-20 (Monday)

  Cost to the customer, 3 days at 4.9%:
    2,400,000 x 0.049 x 3 / 365 = 966.58
  Plus a late-payment penalty under the contract the payment was settling.

Nothing failed. Every component behaved correctly, the payment was screened as required, and the customer lost three days of value because a human review took five minutes at the wrong moment. The synchronous control on the payment path is the cause, and no amount of retrying fixes a closed window.

The design responses are all about the clock rather than the throughput. Cut-offs are modelled as data — per rail, per currency, per business calendar including holidays — and the API rejects or warns at submission time rather than accepting silently. A payment approaching the window is prioritised in the screening queue rather than processed first-in-first-out, because the queue position of a large same-day payment is a commercial fact. And the customer is told the achievable value date at acceptance, since a portal that says "submitted" and means "maybe Monday" is the actual product defect.

The Friday detail is worth stating aloud in an interview. Business-day arithmetic is not calendar arithmetic: rails observe currency-specific holidays, a payment crossing time zones has two calendars, and T+2 means two business days at the settlement location. Teams that implement date + 2 discover this at Easter.

The second-order effect is that back-valuation is sometimes the remedy. Where the delay is the bank's fault, the fix is to post with the original value date and let the interest recalculation run — which is exactly why interest accrual has to be re-runnable for a closed period.

What is reconciliation, and what counts as a break?

Reconciliation is proving that your record of money agrees with somebody else's: the correspondent bank's statement, the card scheme's settlement file, the payment processor's report, the internal suspense account. A break is any difference between the two sides, and the discipline is that every break must be classified and cleared rather than netted off. The classes are few and worth knowing: timing, where an item is in transit and will appear tomorrow; missing, where one side never recorded it; amount, where fees or an intermediary's deduction changed the figure; and duplicate. Breaks matter because they are the only reliable detector of a genuine loss — an unreconciled payment file is how money leaves a bank without anybody noticing — which is why reconciliation is a daily control with an owner rather than a monthly report.

Show me a nostro break investigated.

One value date, one correspondent, three breaks of three different classes, and the arithmetic that has to close exactly.

Nostro 8891 -- our account with the correspondent. Value date 2026-07-13.

our ledger                                  their statement
------------------------------------------  ------------------------------------
CR   250,000.00  INV-4471    settled        CR   250,000.00  INV-4471
DR    18,400.00  PAY-9902    settled        DR    18,400.00  PAY-9902
DR    64,000.00  PAY-9911    settled        --  absent  --
--  not recorded  --                        DR        35.00  CHG-Q2 fees
DR    12,500.00  PAY-9915    settled        DR    12,505.00  PAY-9915

our closing balance     1,455,100.00
their closing balance   1,519,060.00
difference                 63,960.00

reconciled as:
  + 64,000.00   PAY-9911 debited by us, not by them   TIMING
  -     35.00   their quarterly charge, not booked    MISSING
  -      5.00   PAY-9915 amount mismatch             AMOUNT
  = 63,960.00   fully explained, zero unexplained

The last line is the control. A break is not cleared because it is small; it is cleared because it is explained, and the residual after explanation must be zero. An operations team that writes off differences under a tolerance is training itself not to notice the fraud that sits just below the threshold.

Each class needs a different investigation and a different fix. PAY-9911 is timing: we sent it after their cut-off, so it will appear on tomorrow's statement, and the correct treatment is to age it and alert if it does not clear — an item sitting in transit for four days is no longer a timing break, it is a missing one wearing a disguise. The 35.00 charge is a debit we never expected, which means fee bookings from this correspondent are not automated; until they are, the same break recurs every quarter and consumes analyst time on a known cause.

The 5.00 is the interesting one and the reason correspondent banking is unpleasant. An intermediary deducted a lifting fee from the payment in flight, so the beneficiary received less than instructed, and the difference is not an error in either ledger. It has to be recognised as a charge, allocated to whoever agreed to bear it under the charge-bearer instruction, and — if the customer was told they would receive the full amount — made good. This is precisely what ISO 20022's explicit charge information exists to remove.

The engineering deliverables follow from the classes rather than from the totals. Matching is on a reference the counterparty echoes back, not on amount and date, because amount matching fails exactly where fees are deducted. Breaks are aged with escalation thresholds. And every automated match rule records why it matched, because a rule that pairs the wrong two items hides a real loss behind a clean report.

What are nostro and vostro accounts?

They are the same account described from two sides. A nostro is "our account with you" — the balance a bank holds at a correspondent, usually in the correspondent's local currency, so a UK bank keeps a dollar nostro in New York. A vostro is "your account with us", the mirror on the correspondent's books. They exist because a bank cannot hold foreign currency anywhere except at a bank in that currency's system, so cross-border payment is really a sequence of local movements between these accounts. Engineering-wise they are the reason reconciliation matters: your nostro ledger is your own record of a balance somebody else also maintains, the two must agree daily, and the funding of the nostro — having enough dollars in New York before the payment goes out — is a liquidity constraint your payment system has to respect.

Why does end-of-day batch still exist?

Because some computations are defined over a closed period rather than over a stream: interest accrual for the day, fee application, statement generation, regulatory position snapshots, scheme settlement files, and the internal proof that the ledger balances. Each needs a point at which the day is agreed and no longer changes, and that point is a business decision expressed as a cut-off. The tension is that customers expect a 24/7 service while the batch wants a quiet system, which is why banks run a "cut-over" window where the online channel is degraded or read-only. Modern designs shrink rather than remove it — accruing continuously, generating statements from a projection, closing per-region rather than globally — but the concept of an agreed end of day survives because the reporting obligations are defined that way.

Payments and clearing

What is the difference between clearing and settlement?

Clearing is the exchange and agreement of payment instructions: who owes whom, how much, netted or gross, with validation and reconciliation of the files. Settlement is the actual transfer of funds between the banks' accounts, almost always across accounts held at a central bank. They are separate because clearing can happen in bulk and cheaply while settlement consumes central-bank liquidity, so a batch system clears thousands of items and settles one net figure. The engineering consequence is that a payment your customer sees as complete may be cleared but not settled, and the bank carries the counterparty risk in between. A candidate who uses the two words interchangeably will not be able to answer when the money is actually irrevocable, which is the follow-up.

Show me the payment rails compared on speed, finality, cost and reversibility.

The choice of rail determines what your system must handle, so knowing the shape of each is a vocabulary test with real design consequences.

rail             speed          finality          cost      reversible?
---------------  -------------  ----------------  --------  --------------------------
Card networks    auth seconds,  none at auth,     high, %   yes -- chargebacks for
                 settle T+1/2   settled next day  of value  months after the payment

ACH-style batch  hours to       on settlement     very low  yes -- returns for days,
                 T+1/T+3        day, revocable    per item  unauthorised-debit rules
                                during the window

RTGS             seconds to     immediate and     high per  no -- recall is a request
                 minutes        irrevocable       item      to the beneficiary bank

Faster payments  seconds,       immediate for     low       no -- only by consent
                 24/7/365       the payer         per item

SWIFT via        hours to days  per leg, at each  high plus  no -- each leg is final
correspondents   over 2-3 legs  intermediary      FX spread  where it settled

SEPA SCT         next business  on settlement     very low  recall by request only
SEPA SCT Inst    under 10s      immediate         low       no

UPI              seconds,       immediate         near zero no -- dispute process only
                 24/7           

Read the finality column first, because it decides your architecture. On an irrevocable rail there is no compensating transaction, so every control — balance check, limit check, sanctions screening, fraud scoring, confirmation of payee — must complete before submission, and the whole payment flow becomes a saga whose last step cannot be undone. On a reversible rail you can afford to be optimistic and correct afterwards, which is why card systems tolerate risk decisions that a faster-payments system cannot.

The reversibility column is the mirror image and it is where the money is. A card payment can come back one hundred and twenty days later through a chargeback, so an acquirer's ledger must hold a liability for transactions it has already paid out on. An ACH-style debit can be returned as unauthorised, so a merchant treating a successful debit as revenue is recognising money it may not keep. Instant rails have neither mechanism, which is why authorised-push-payment fraud is a faster-payments problem rather than a card one.

Cost drives design more than candidates expect. At a fraction of a penny per item you batch and forget; at a meaningful fee per item you route, net internally and choose the rail by amount and urgency — which is what a payment orchestration layer is actually for.

The operating window is the last column nobody lists. A 24/7 rail means there is no quiet period for a batch, no maintenance slot, and no natural end of day, and that single fact is what forces a core banking migration more often than volume does.

What is payment finality, and why does irrevocability change your design?

Finality is the point at which a transfer cannot be undone by the sender, the sending bank or an insolvency proceeding — it is a legal property conferred by the scheme's rules, not a status flag you choose. It changes design because everything after it must be compensated at the business level rather than rolled back: recovering funds from an irrevocable payment means asking the beneficiary bank to return them, and the beneficiary may simply refuse. So the ordering of your pipeline is dictated by finality. Validation, limits, screening and fraud checks sit before the point of no return; notifications, ledger projections and downstream events sit after it and must be retryable. Getting this backwards produces a system that emails the customer, then discovers it cannot fund the payment.

Show me a duplicate payment prevented by an idempotency key.

The uniqueness constraint is the mechanism. Everything else — the header, the middleware, the retry policy — is plumbing around it.

CREATE TABLE payment (
  payment_id       uuid PRIMARY KEY,
  initiator_id     text        NOT NULL,
  idempotency_key  text        NOT NULL,
  request_hash     text        NOT NULL,
  amount_minor     bigint      NOT NULL,
  currency         char(3)     NOT NULL,
  state            text        NOT NULL,
  end_to_end_id    text        NOT NULL,
  rail_reference   text,
  created_at       timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT payment_idem UNIQUE (initiator_id, idempotency_key)
);
Client submits, times out, retries the identical request.

t0  POST /payments  Idempotency-Key: 7c2f-a91  amount 250000
    INSERT -> succeeds, payment_id p-9001, state ACCEPTED
    response lost in the network

t1  POST /payments  Idempotency-Key: 7c2f-a91  amount 250000
    INSERT -> unique violation on payment_idem
    SELECT the existing row, compare request_hash: identical
    respond 200 with p-9001 and its current state -- no second payment

t2  POST /payments  Idempotency-Key: 7c2f-a91  amount 999000
    INSERT -> unique violation
    request_hash differs from the stored hash
    respond 422 -- the key has been reused for a different request

The constraint, not the check, is what makes this correct. A SELECT before INSERT leaves a window in which two concurrent retries both find nothing and both proceed, and that window is measured in microseconds precisely when a client is hammering a timed-out endpoint. The database is the only component that can serialise the decision, so the duplicate is detected by a failed insert rather than prevented by a lookup.

The request_hash is the part candidates leave out and interviewers ask about. A key alone protects against retries; it does not protect against a client reusing a key for a different payment, whether through a bug or a truncated key space. Storing a hash of the material fields and rejecting a mismatch converts a silent wrong-amount payment into a 422.

Scoping the key by initiator_id matters because keys are client-generated. Two tenants will eventually choose the same UUID-like string, and a global unique index turns that into one tenant's payment being returned to another — an information leak on top of a failed payment.

The second layer is the rail itself, and this is where banking differs from a generic API. Your idempotency key protects your service; it means nothing to the scheme. The end_to_end_id — the UETR on an ISO 20022 message — is the scheme-level identifier that lets the receiving bank detect a duplicate submission, so it must be generated once and reused on every resubmission of the same payment. A retry that mints a fresh end-to-end reference is a genuinely new payment as far as the rail is concerned, and that is how a customer gets debited twice by a system that had idempotency keys throughout.

What is the difference between a return, a reversal and a recall?

A return is initiated by the receiving side, usually because the payment cannot be applied — account closed, name mismatch, unauthorised debit — and it is a new payment in the opposite direction with a reason code, not an undo. A reversal is initiated by the sender and is generally only possible before finality, or within a scheme-defined window for a technical error such as a duplicate file. A recall is a request to the beneficiary bank to send the money back after finality, which the beneficiary bank may decline and which usually needs the beneficiary's consent. The engineering point is that all three appear as new movements in the ledger, linked to the original, and none of them deletes anything. Treating a return as "the payment failed" gets the customer's statement wrong.

What is ISO 20022, and why does the migration matter?

A standard for financial messaging built on a shared data dictionary and XML schemas, replacing decades of fixed-format messages such as SWIFT MT and the domestic equivalents. It matters because the old formats were length-limited and largely free-text, so party names, addresses and remittance information arrived as unparsed strings. Structured data changes what software can do: sanctions screening against a real address rather than a blob, automatic reconciliation against invoice numbers carried end to end, a unique end-to-end reference that survives every hop, and richer purpose codes for regulatory reporting. The migration is painful because both formats coexist for years, translation between them truncates data, and a truncated field cannot be recovered downstream — which is why "we map ISO 20022 to our internal MT-shaped model" is the decision that throws away the entire benefit.

Show me an ISO 20022 payment against its legacy equivalent.

The same instruction in both formats, with the fields that only exist in one of them.

Legacy MT103 -- fixed tags, length limited, mostly free text

:20:REF12345678
:32A:260714GBP250000,00
:50K:/12345678
ACME LIMITED
UNIT 4 SOMEWHERE ROAD LONDON
:59:/87654321
BETA TRADING LTD
:70:INV 4471 4472 PARTIAL SEE EMAIL FOR SPLIT
:71A:SHA
<CdtTrfTxInf>
  <PmtId>
    <EndToEndId>ACME-INV-4471-4472</EndToEndId>
    <UETR>b4c8e1f2-3a77-4d19-9c02-7f1a5d6e8b30</UETR>
  </PmtId>
  <IntrBkSttlmAmt Ccy="GBP">250000.00</IntrBkSttlmAmt>
  <ChrgBr>SHAR</ChrgBr>
  <Dbtr>
    <Nm>ACME LIMITED</Nm>
    <PstlAdr><BldgNb>4</BldgNb><StrtNm>Somewhere Road</StrtNm>
      <TwnNm>London</TwnNm><PstCd>EC1A 1BB</PstCd><Ctry>GB</Ctry></PstlAdr>
    <Id><OrgId><LEI>213800XYZABC12345678</LEI></OrgId></Id>
  </Dbtr>
  <Purp><Cd>TRAD</Cd></Purp>
  <RmtInf>
    <Strd><RfrdDocInf><Nb>4471</Nb></RfrdDocInf>
      <RfrdDocAmt><DuePayblAmt Ccy="GBP">150000.00</DuePayblAmt></RfrdDocAmt></Strd>
    <Strd><RfrdDocInf><Nb>4472</Nb></RfrdDocInf>
      <RfrdDocAmt><DuePayblAmt Ccy="GBP">100000.00</DuePayblAmt></RfrdDocAmt></Strd>
  </RmtInf>
</CdtTrfTxInf>

Three differences do actual work. The structured address turns screening from a string-similarity problem into a comparison with country, town and postcode as discriminators, which is the single largest lever on the false-positive rate. The structured remittance information carries the invoice split, so the beneficiary's accounts-receivable system can apply 150,000 to one invoice and 100,000 to another without a human reading an email. And the UETR is one identifier the payment keeps across every intermediary, which is what makes end-to-end tracking and duplicate detection possible at all.

The legacy message can express none of that. :70: is seventy characters of free text per line and the split is described in prose, so the receiving system posts 250,000 to a suspense account and someone reconciles it by hand. That manual step is the cost the migration is meant to remove, and it is why the business case is made in operations headcount rather than in engineering elegance.

The trap in the migration is the internal model. If your canonical representation was designed around the MT fields, every ISO message is lossily downgraded at ingest and the structured data is gone before any of your services see it. The correct sequence is to widen the internal model first, then the interfaces — which is expensive, unglamorous, and the reason these programmes run for years.

Coexistence adds the last constraint. During the transition a payment may enter as ISO and leave as MT, so truncation rules must be explicit and recorded, and the receiving party has to be told that data was dropped rather than discovering a name cut to thirty-five characters.

What do IBAN and BIC actually give you?

An IBAN identifies an account: country code, two check digits, then a country-specific domestic portion including the bank and branch identifiers. Its value is the check digits, which are a mod-97 checksum you can validate offline, catching most transcription errors before a payment leaves your system — and validating them client-side is one of the cheapest error-reduction measures in payments. A BIC identifies an institution, and optionally a branch, which is how a message is routed to the right bank. What neither gives you is confirmation that the account exists or that the name matches the account holder, which is exactly the gap authorised-push-payment fraud exploits and the gap confirmation-of-payee schemes were introduced to close.

Why is a 24/7 instant rail harder than a batch one?

Because it removes every assumption a traditional core banking system was built on. There is no quiet window, so batch processing has to coexist with live traffic; there is no end of day, so accrual and reporting boundaries become logical rather than operational; and liquidity must be managed continuously, because you cannot fund a settlement account at nine the following morning for payments going out at three on Sunday. Availability requirements change too: a rail that is always open means an outage is always customer-visible, and the scheme may fine you for missing an availability target. Latency budgets are hard rather than aspirational, since a scheme response deadline measured in seconds includes your fraud scoring, your sanctions screening and your balance check.

What is confirmation of payee, and what does it change?

A check, before a payment is sent, that the name the payer typed matches the name on the destination account, returning a match, a close match with the actual name, or no match. It exists because IBAN and sort code plus account number carry no identity information, so a fraudster only has to persuade someone to type the right numbers. Engineering-wise it introduces a synchronous, latency-sensitive call to a directory service on the payment path, with fuzzy name matching and its own false positives, plus a hard product decision: what you do when the payer proceeds despite a mismatch. It also shifts liability, since a bank that failed to warn may be required to reimburse, which makes the response and the customer's acknowledgement a record you must retain rather than a UI hint.

Cards and acquiring

What is the four-party model, and who actually holds the money?

Four parties plus the scheme in the middle: the cardholder, the issuer that gave them the card and holds their account, the merchant, and the acquirer that holds the merchant's account and connects them to the network. The scheme — Visa, Mastercard — sets the rules and routes messages but is not a party to the funds. The money flows issuer to scheme to acquirer to merchant, net of interchange and fees, a day or more after the cardholder saw the transaction. The reason this matters to engineering is that no single party has the whole picture: the merchant sees an approval, the acquirer sees a settlement file, the issuer sees a hold and later a clearing record, and reconciling them is a daily job at every one of the four.

Show me an authorisation and a later capture across the four parties.

The two-phase shape is the thing to internalise, because almost every card bug comes from treating it as one step.

sequenceDiagram
    participant C as Cardholder
    participant M as Merchant and acquirer
    participant N as Card network
    participant I as Issuer
    C->>M: Presents card, amount 42.00
    M->>N: Authorisation request with token and amount
    N->>I: Routed by the issuing BIN
    I-->>N: Approved, auth code 7731, hold placed
    N-->>M: Approved
    M->>N: Capture in the evening batch
    N->>I: Clearing record, settlement the next day

The authorisation is a promise, not a movement. The issuer checks funds, limits and fraud risk, places a hold, and returns an auth code within a second or two, but no money has moved and nothing has been posted to the ledger. The merchant has permission to collect, valid for a scheme-defined window.

The capture is the request to actually collect, usually batched at the end of the merchant's day, and it may be for less than the authorised amount — a partial shipment, a hotel bill lower than the pre-authorisation — or for slightly more where the category permits a tip tolerance. Settlement follows on the scheme's cycle, which is why a merchant's bank balance lags their terminal report by a day or two and why acquirer reconciliation exists.

Three failure modes follow directly from the gap between the two phases. A capture without a matching authorisation is a forced post, which the issuer must generally honour and which lands on an account whose availability was released. An authorisation never captured leaves a hold that expires, which is the customer complaint about money being "taken twice". And a partial capture that fails to release the remainder of the hold is functionally the same complaint with a different cause.

The design rule that falls out is to model authorisation and capture as separate entities with an explicit link, never as one payment row with a status. Systems that use a single row cannot represent a partial capture, multiple captures against one authorisation, or a reversal, and the workaround is invariably a second row that breaks reconciliation.

What is interchange, and who really pays it?

Interchange is the fee the acquirer pays the issuer on each card transaction, set by the scheme and varying by card type, channel, merchant category and region — a premium consumer credit card taken online costs far more than a domestic debit card at a terminal. It funds the issuer's rewards, credit risk and fraud losses. The merchant pays it indirectly through the acquirer's merchant service charge, and ultimately prices it into goods. It matters to engineers because it makes the economics of a transaction depend on data you can influence: passing better authentication data, correct merchant category codes and complete address information can move a transaction into a cheaper interchange tier, and getting them wrong quietly raises the cost of every sale.

What is a chargeback?

The mechanism by which an issuer takes funds back from an acquirer on the cardholder's behalf, under a reason code — fraud, goods not received, not as described, duplicate processing, subscription not cancelled. It is a scheme process with fixed time limits and evidence requirements, not a refund: a refund is the merchant choosing to return money, a chargeback is the money being removed whether the merchant agrees or not, with a fee attached. For engineering it means a transaction is never truly done. An acquirer must hold a liability against settled transactions for months, a merchant needs the evidence to defend a dispute retrievable long after the sale, and a fraud model's real feedback signal arrives weeks late, which is what makes card fraud a delayed-label learning problem.

Show me the dispute lifecycle with its time limits.

The limits are the design constraints, because each one is a deadline your system must meet without a human noticing it first.

flowchart TD
    A[Cardholder disputes<br/>usually within 120 days] --> B[Issuer raises a chargeback<br/>with a reason code]
    B --> C[Acquirer debits the merchant<br/>and requests evidence]
    C --> D{Merchant represents<br/>within 30 days?}
    D -->|no| E[Chargeback stands<br/>and the merchant loses the funds]
    D -->|yes| F[Second presentment<br/>and issuer review]
    F --> G[Funds return to the merchant]
    F --> H[Pre-arbitration then arbitration<br/>where fees can exceed the amount]

Every arrow is a clock. Missing the representment window loses the money regardless of the merits, which means the dispute inbox is a system with a service level rather than a mailbox, and the most common cause of loss is not a weak case but an unanswered notification.

The evidence requirement drives retention. Defending "goods not received" needs the delivery confirmation, the address used, the authentication result and the transaction detail, all retrievable months later — so a system that prunes fulfilment data after ninety days has silently decided to lose disputes. Retention here is a commercial control, not a compliance one.

The economics decide the strategy. Arbitration fees frequently exceed the disputed amount, so for small transactions the rational choice is to accept, and the useful engineering output is a rule that triages by amount, reason code and evidence strength rather than routing everything to a human. Chargeback ratios also matter in themselves: exceeding a scheme threshold puts a merchant into a monitoring programme with fines and, eventually, loss of acceptance, which is why the ratio is a monitored metric and not just a cost line.

The pre-emptive control is worth naming. Most disputes are avoidable — a clear descriptor on the statement so the cardholder recognises the charge, a proactive refund when delivery fails, and an alert to the issuer before the dispute is raised. Preventing a chargeback costs a fraction of defending one, and the descriptor is a one-line configuration nobody owns.

What is the difference between a PSP token, a network token and a PAN?

The PAN is the card number itself, and everything in card engineering is about not holding it. A PSP token is a reference issued by your payment provider, valid only for your account with them, which removes the PAN from your systems but locks you to that provider and dies if the underlying card is reissued. A network token is issued by the scheme, tied to the card rather than to a merchant relationship, and is automatically updated when the card is replaced — so a subscription keeps working after a lost card, and authorisation rates measurably improve. The engineering trade is portability and lifecycle against dependency: PSP tokens are easy to adopt and hard to migrate away from, network tokens require scheme enrolment and give you a credential that survives both card reissue and, in principle, a change of provider.

What is 3-D Secure, and what does SCA add to it?

3-D Secure is the protocol that lets an issuer authenticate a cardholder during an online purchase, either silently from device and transaction data or by challenging them, and it shifts liability for fraud from the merchant to the issuer when it is used. Strong customer authentication under PSD2 makes that authentication mandatory for most European electronic payments, requiring two independent factors. The consequence for engineering is that checkout stops being a single synchronous call: you initiate, the issuer may challenge, the customer completes it out of band, and you resume — so payment state must be persisted and resumable across a redirect the customer may abandon. Using the exemptions correctly is real work, because low-value, recurring and merchant-initiated transactions can skip the challenge, and each exemption you claim moves liability back to you.

Lending and credit

What does a credit decisioning engine actually do?

It assembles data, applies policy, scores, and produces a decision with a recorded reason. The data comes from the application, bureau files, open-banking transaction data, internal history and fraud checks. Policy rules are the hard gates — minimum age, maximum exposure, adverse-record exclusions — and they run before scoring because a policy decline needs no model. The score then drives an approve, decline or refer outcome, with terms attached: limit, rate, and any conditions. The engineering characteristics that matter are that every decision must be reproducible from the inputs and the versioned rules and model that produced it, that referrals to a human underwriter are a designed path with service levels, and that the reason codes are customer-facing, because an applicant is generally entitled to know why.

What constrains the choice of a credit scoring model?

Explainability and fairness constrain it more than accuracy does. A declined applicant may be legally entitled to the principal reasons for the decision, so a model whose output cannot be attributed to specific factors is difficult to deploy regardless of its lift. Protected characteristics cannot be used, and neither can proxies for them, which means feature selection is a compliance exercise as well as a statistical one and disparate-impact testing is part of the release process. Stability matters too: a model retrained on a shifting population changes decision boundaries for people who were previously approved, so challenger models run in shadow before promotion. The practical result is that regulated lenders still run scorecards and gradient-boosted models with attribution rather than whatever scores best offline.

Show me interest accrued under three day-count conventions.

The convention is a contractual term, not an implementation detail, and the difference is real money on real balances.

Principal   250,000.00      Nominal rate  5.25%
Period      1-31 July 2026  Actual days   31

Annual interest at nominal rate:  250,000.00 x 0.0525 = 13,125.00

ACT/365   daily = 13,125.00 / 365 = 35.958904
          July  = 35.958904 x 31   = 1,114.73

ACT/360   daily = 13,125.00 / 360 = 36.458333
          July  = 36.458333 x 31   = 1,130.21

30/360    days counted as 30 regardless of the calendar
          July  = 13,125.00 x 30 / 360 = 1,093.75

Spread, one month:   1,130.21 - 1,093.75 = 36.46
Spread, one year on ACT/360 versus ACT/365:
   13,125.00 x (365/360 - 1) = 182.29   -- 1.39% more interest, same quoted rate

ACT/360 charges the borrower more than ACT/365 at the same quoted rate, because the daily figure is divided by a shorter year and then multiplied by the actual number of days. That is not a trick; it is a convention with a history, and it is why comparing two loans by their headline rates alone is meaningless. A candidate who knows that a "5.25% loan" has a different cost under two conventions demonstrates that they read the contract rather than the marketing.

30/360 flattens every month to thirty days, which makes payment schedules uniform and predictable — useful for bonds and fixed-instalment loans — at the cost of being wrong about the calendar. February gets thirty days of interest and July gets thirty as well, so the annual total works out but no individual month is accurate.

The engineering consequences are three. Accrual must be a stored daily figure per account rather than a computed balance, because the audit question is "how much accrued on the fourteenth" and because capitalisation compounds on the accrued amount. Rounding must be applied at a defined point with a defined rule — half-up at the minor unit, per day, not per period — and specified in one place, since rounding a daily accrual differently from a monthly total is a permanent reconciliation break. And a back-valued posting requires the whole period to be re-accrued, which means the calculation has to be idempotent and re-runnable over a closed period rather than a nightly job that appends.

The last detail is the year boundary in ACT/ACT and its variants, where a period spanning a leap year is split and each part divided by its own year length. Teams that hard-code 365 discover this in February 2028.

What is exposure, and why is a limit hard to enforce?

Exposure is the total amount a bank could lose to one counterparty, which is not the same as the outstanding balance: it includes undrawn commitments, the credit limit on a card whether used or not, contingent items such as guarantees, and settlement exposure on payments in flight. Limits are hard to enforce because exposure aggregates across products and legal entities that live in different systems, and the correct unit is a group of connected counterparties rather than a single customer — a limit applied per account is trivially bypassed by opening two accounts. Real-time enforcement is harder still, because a decision must be made in a second against a figure that requires querying several cores, so most banks enforce against a near-real-time cached aggregate and accept a small window of overshoot deliberately.

What does loan servicing involve after the money is lent?

Everything between disbursement and closure, and it is where most of the code lives. Schedule generation and regeneration, direct-debit collection with its returns, payment allocation in the contractual order — fees, then interest, then principal — interest accrual and capitalisation, rate changes on a variable product, statements and regulatory notices with prescribed timing, overpayments and early settlement with its penalty calculation, payment holidays, and restructures that rewrite the schedule while preserving the history. Two properties make it demanding: the arithmetic must be exact and reproducible years later, and almost every event can be back-dated, so the servicing engine has to recompute a period rather than move forward only. That is why loan systems look like event-sourced calculators rather than CRUD applications.

What is delinquency and collections in data terms?

Delinquency is a state machine driven by days past due, with buckets — one to thirty, thirty-one to sixty, and so on — that trigger prescribed actions: notices, fees, reporting to the credit bureau, and eventually default, charge-off and possible sale of the debt. The engineering demands are precision in the clock, because a bucket transition has legal and reporting consequences and "thirty days" must account for business days, holidays and the payment's value date, and completeness of the audit trail, because a wrongly reported default harms someone materially and may have to be corrected at the bureau. Collections adds treatment strategy — contact channel, timing, affordability assessment and forbearance — which is where regulation on vulnerable customers turns a prioritisation model into a set of hard constraints on what you may do.

Regulation that shapes systems

Which regulations most change a banking system's shape?

Four families, each with a distinct architectural fingerprint. Prudential rules — the Basel framework as implemented locally — demand risk and capital calculations over positions, which forces data lineage from the ledger to the report. Financial-crime rules force identity verification, screening, monitoring and case management with retention measured in years. Payment-services rules, notably PSD2 and its successors, force strong authentication, consent-based third-party access and standardised APIs. And conduct and consumer rules dictate the timing and content of communications, affordability assessment, and treatment of vulnerable customers, which lands as validation and workflow rather than as reporting. Knowing which family a requirement belongs to tells you which part of the system it will change, and that is what an interviewer is checking.

What does a regulatory report demand of your data lineage?

That every figure can be traced back to the transactions that produced it, and reproduced later. In practice that means a report is generated from an agreed point-in-time position rather than from live tables, that the transformations between the ledger and the submitted number are versioned code with recorded inputs, and that the report reconciles to the general ledger with any difference explained. Restatement is the requirement people forget: when an error is found, you must be able to reproduce what was submitted, produce what should have been submitted, and explain the delta — which is impossible if the pipeline reads mutable tables. So regulatory reporting pushes towards immutable snapshots, effective dating, and jobs that are functions of a dated input set rather than of whatever the database contains today.

What is Basel capital reporting, in engineering terms?

A calculation over every exposure the bank holds, producing risk-weighted assets and the capital that must be held against them, plus liquidity ratios measuring whether the bank survives a stress period. Engineering-wise it is an aggregation problem with brutal data requirements: every exposure needs a counterparty, a rating, collateral, maturity and product classification, sourced from systems that were not built to supply them consistently. The counterparty hierarchy is the recurring difficulty, since exposures must roll up to a group and the group structure lives in reference data of variable quality. Because the output is reported and audited, the calculation must be reproducible for any past date, which makes point-in-time reference data — the rating as it stood on that date, not as it stands now — a first-class requirement rather than a nicety.

What must you retain for a single payment instruction, and for how long?

More than the payment row. The instruction as received including the channel and the authenticating credential, the sanctions screening result with the list version and any analyst decision, the fraud score and any customer warning displayed and acknowledged, the messages exchanged with the rail and their references, the ledger postings, and any subsequent return, recall or dispute. Retention is typically five to seven years after the relationship ends, driven by anti-money-laundering record-keeping and by limitation periods for disputes. The design consequence is that a payment is an aggregate assembled from several systems, and the retention obligation applies to all of them — so purging the screening service's history after ninety days to save storage destroys the evidence that the payment was lawfully released.

What is a suspicious activity report, and what may you not do?

A report filed with the national financial-intelligence unit when a firm knows or suspects money laundering or terrorist financing, following investigation of an alert. What you may not do is tell the customer: tipping off is a criminal offence in most jurisdictions, which has direct product consequences. You cannot show a reason for a delay, you cannot expose an internal case status through the app or to a call-centre agent whose script would reveal it, and you may have to continue serving the customer normally while the report is with the authorities. Engineering must therefore keep financial-crime case data in a separate access domain with its own permissions and its own audit trail, invisible to ordinary support tooling, and the customer-facing states must be designed so that "blocked pending investigation" and "blocked for a technical reason" are indistinguishable.

Core banking and integration

What is a core banking system, actually?

The system of record for accounts, balances, postings, products, interest and end-of-day processing — the ledger and the machinery immediately around it. It is usually a vendor package, frequently decades old, often batch-oriented, and it is the thing everything else in the bank reads from or writes to. Its characteristics shape every project: limited throughput and a fixed batch window, an integration surface that may be files or a proprietary protocol rather than an API, no concept of a partial or reversible operation beyond its own transaction model, and a change process governed by the vendor's release cycle. Replacing it is a multi-year programme with no business feature at the end, which is why banks build around it instead, and why the strangler pattern is the default answer.

Show me a strangler around a core banking system.

The point of the pattern is that the ledger of record does not move until everything else has, and possibly not even then.

flowchart LR
    A[Channels: app, web, branch] --> F[Facade owns the external contract]
    F --> N[New services: payments,<br/>limits, notifications]
    F --> C[Core banking: accounts,<br/>interest, end of day]
    N --> L[Ledger of record<br/>stays in the core]
    C --> L
    F --> S[Shadow compare new against core<br/>before switching each read]

The facade is the whole strategy. Channels stop calling the core directly and call a contract you own, which means each capability can move behind it without any client changing. Skipping the facade is why so many of these programmes stall: without it, moving one function means coordinating a release across the app, the web front end, the branch terminal and three internal consumers.

The order of migration is reads first, then non-ledger writes, then ledger writes if ever. Reads are safe because you can shadow them — serve from the core, compute from the new service, compare, alert on divergence — and a week of divergence data is worth more than any amount of design review. Non-ledger writes such as limits, preferences and notifications carry no money risk. Ledger writes are last because they cannot be shadowed in the same way: you cannot double-post to compare.

The invariant to hold is single ownership of each fact. A period where both the core and the new service believe they own the balance is the failure mode, and it does not present as an error — it presents as a slow divergence discovered in reconciliation weeks later. So each capability has one owner at all times, with a dated cut-over, and the losing side becomes a projection rather than remaining a writer.

What kills these programmes, and worth saying unprompted, is that the core's batch still runs. New services must respect the same cut-off, the same business calendar and the same end-of-day quiet period, so the constraints you were trying to escape apply to the new code from its first day. Escaping them requires retiring the batch, which is the actual project, and it is generally the last thing anybody funds.

Why can a bank not treat eventual consistency the way a social feed does?

Because a lost or duplicated write is a loss of money with a legal owner, and the compensating action is not always available. A stale feed shows an old post; a stale balance authorises a payment that cannot be funded, and on an irrevocable rail there is no undo. So the ledger itself is strongly consistent within its boundary, with the account as the consistency unit, and eventual consistency is used deliberately for things that can tolerate it — statements, search, analytics, notifications, most reporting. The mature answer is not "banks need strong consistency everywhere" but that the ledger is the one place where you pay for serialisability and derive everything else asynchronously, with the derived views labelled as of a time so nobody mistakes a projection for the record.

What does a payment orchestration layer own?

The lifecycle of a payment across rails: validation, enrichment, routing to the cheapest or fastest rail that meets the requirement, the sequencing of controls before the point of finality, submission with idempotency, status tracking, and the handling of returns and recalls. It exists because rails differ in message format, operating hours, cut-offs, finality and cost, and every channel would otherwise implement those differences itself. Its state machine is the artefact that matters — accepted, screened, held, submitted, settled, returned — persisted rather than in memory, because a payment can sit in a state for days while somebody investigates. The trap is letting it become a distributed transaction coordinator across the ledger and the rail, which it cannot be, since the rail does not participate in your transaction.

Why are the batch window and online availability in tension?

Because the batch wants a stable, unchanging picture of the day and the customer wants to move money at three in the morning. Traditional cores resolve it by closing the online channel or degrading it to read-only during cut-over, which is acceptable when the rails are also shut and unacceptable once an instant rail runs continuously. The intermediate designs are worth knowing: queue transactions arriving during cut-over and apply them to the new day, accrue interest continuously so the accrual job has nothing to do, snapshot rather than freeze so reporting reads an immutable copy while the live system moves on, and stage the close per region or per ledger rather than globally. Each shrinks the window without removing the concept of an agreed end of day.

What happens inside a bank when a payment rail goes down?

Payments queue, and the decisions become commercial rather than technical. You must decide whether to accept instructions you cannot execute, which means holding customer funds against a payment that has not gone; whether to reroute to a different rail with a different cost and finality profile; and how to communicate a delay that may breach the customer's own contractual deadline. Meanwhile inbound payments are also stopped, so balances are wrong in the sense of being incomplete, and any process reading them — limit checks, collections, overdraft fees — is making decisions on partial information. The engineering requirements are a durable queue with an explicit accepted-but-not-submitted state, suppression of downstream automation that assumes completeness, and a recovery path that respects cut-offs and value dates when the rail returns.

Interview traps

Is a payment atomic across the bank and the rail?

No, and any design that assumes it is will lose money. The rail is an external system that does not participate in your database transaction, so there are always two commits with a window between them, and the window contains every interesting failure: submitted and not recorded, recorded and not submitted, submitted with an unknown outcome. The correct shape is a persisted state machine with the ledger movement and the rail submission as separate steps, an outbox so the intent survives a crash, idempotent submission keyed by an identifier the rail also understands, and a reconciliation process that resolves unknowns against the scheme's records rather than by guessing. Saying "we would wrap it in a distributed transaction" is the answer that ends the discussion badly.

Does caching a balance mean you have abandoned double entry?

No, provided the cache is a derived projection and the postings remain the record. The distinction interviewers listen for is which artefact is authoritative: a daily snapshot plus subsequent postings is a legitimate optimisation because it can be rebuilt and proved from the rows, whereas a balance column mutated by the application is a second source of truth that will eventually disagree with the ledger and cannot be reconstructed. The tests are whether the cached figure can be recomputed from postings at any past date, whether a discrepancy between the two is detected automatically rather than by a customer, and whether a correction posts a reversing entry or edits the number. Fail those and double entry is decorative.

Is a card payment complete once the authorisation is approved?

No, and the assumption that it is causes a specific family of bugs. Authorisation places a hold and grants permission to collect; the money moves at capture and settles later, the captured amount may differ from the authorised one, the hold expires on a scheme timetable, and the transaction remains disputable for months afterwards. So a merchant that ships on authorisation is taking a risk it may not have priced, an accounting system that recognises revenue at authorisation will disagree with the settlement file, and an issuer that posts at authorisation double-counts when the clearing record arrives. The safe framing is that authorisation is a promise with an expiry, capture is a claim, settlement is the money, and the dispute window means nothing is final for a long time.

Is a sanctions false positive a bug?

No, and treating it as one leads to exactly the wrong engineering. The matching is deliberately fuzzy because the alternative is missing a designated party using a transliterated name, so a high false-positive rate is the intended operating point of a control whose costs are asymmetric. What is a bug is the same false positive recurring monthly because resolved matches are not whitelisted, an alert queue with no prioritisation so a large same-day payment misses its cut-off behind a trivial one, screening against a list version you cannot reproduce later, and a matching engine that ignores the date of birth and country you already hold. Improving precision with better data is the work; moving the threshold to shrink the queue is a compliance failure with an engineering justification attached.

What is an interviewer testing when they ask how you would guarantee a payment is not processed twice?

Whether you know that duplicate prevention is a chain of mechanisms at different boundaries rather than one feature. A strong answer begins with the three outcomes of a remote call, success, failure and unknown, since the unknown case forces a retry and the guarantee must therefore come from deduplication. It then walks the boundaries: a client-generated idempotency key scoped to the initiator and enforced by a uniqueness constraint, with a request hash so a reused key is rejected; an outbox so the intent survives a crash between the ledger commit and the rail submission; a scheme-level end-to-end identifier reused on every resubmission so the receiving bank detects the duplicate; and a reconciliation for payments whose outcome you never learned.

A weak answer offers one layer and stops, usually an idempotency key with nothing said about what enforces it. Worse is a distributed transaction across the ledger and the rail, or a SELECT before INSERT, which is a race with an expensive outcome. The signal is whether the candidate reaches for a detective control at all, because prevention is always incomplete and the reconciliation is what tells you it failed.