Reported November 2023
Rampdesign

Banking System with Accepted Transfers, Part 4: Account Merging and Balance History

Reported by candidates from Ramp's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Ramp OA. Under 2s to a working solution.
Founder's read

Ramp's November 2023 OA is a four-part banking system, and Part 4 is where the quiet bugs live. Merging accounts looks like adding two numbers until a pending transfer points at the account you just closed. This is a design problem, not an algorithm puzzle. You build a class that handles CREATE_ACCOUNT, DEPOSIT, PAY, TOP_ACTIVITY, TRANSFER, ACCEPT_TRANSFER and MERGE_ACCOUNTS, and every earlier level has to keep working. If you blank on the merge rules mid-assessment, StealthCoder is the invisible safety net that reads the problem and hands you a path forward.

The problem

Implement a simplified banking system. The assessment is cumulative: after unlocking a new level, every operation from the current and previous levels remains available.
Query and Output Rules
Each query calls exactly one operation and includes a stringified millisecond timestamp.
Timestamps are unique, lie between 1 and 10^9, and appear in strictly increasing order.
Return one string result for every query, in input order.
Return the empty string when an operation has no successful scalar result, exactly as specified below.
Multipart Series
Part 1: Accounts and Payments
Part 2: Activity Ranking
Part 3: Pending Transfer Acceptance
Part 4: Account Merging and Balance History
Level 1: Accounts and Payments
The banking system should support creating new accounts, depositing money, and withdrawing or paying money from accounts.
CREATE_ACCOUNT <timestamp> <accountId> creates a new account with the given accountId if it does not already exist. Return "true" if the account is created and "false" if it already exists.
DEPOSIT <timestamp> <accountId> <amount> deposits amount into the account. Return the account balance after processing the query, or the empty string if the account does not exist.
PAY <timestamp> <accountId> <amount> withdraws amount from the account. Return the account balance after processing the query. Return the empty string if the account does not exist or has insufficient funds.
Level 2: Activity Ranking
The banking system should support ranking accounts by the total value of their transactions.
TOP_ACTIVITY <timestamp> <n> returns the top n accounts with the highest total transaction value, sorted by total value descending and then by accountId alphabetically ascending.
Return one string in the format "<accountId1>(<transactionsValue1>),..., <accountIdN>(<transactionsValueN>)".
Total transaction value is the sum of every processed amount for an account, regardless of how it changes the balance: deposits, payments, and each side of a successfully accepted transfer count.
If fewer than n accounts exist, return all active accounts in the same format.
Level 3: Pending Transfer Acceptance
The banking system should allow transfers to be scheduled and their status to be resolved by the target account.
TRANSFER <timestamp> <sourceAccountId> <targetAccountId> <amount> initiates a transfer. Withdraw amount from the source immediately and hold it until the target accepts the transfer or it expires. Refund the held money to the source when it expires.
Return the empty string when the source equals the target, either account is absent, or the source has insufficient funds.
A transfer expires after 24 * 60 * 60 * 1000 = 86400000 milliseconds. It expires at the beginning of the next millisecond after that period, so acceptance at the exact expiration timestamp is still valid.
A valid transfer returns the next global ID: "transfer1", "transfer2", and so on. Failed transfers do not consume an ID.
The source and target transaction histories are updated only after acceptance. A successfully accepted transfer contributes its amount to the total transaction value of both accounts.
ACCEPT_TRANSFER <timestamp> <accountId> <transferId> accepts a pending transfer. Return "true" on success. Return "false" if the transfer does not exist, was already accepted, expired, or accountId is not its target.
Level 4: Account Merging and Balance History
The visible source specifies that Level 4 merges two accounts while retaining the balances and transaction histories of the original accounts.
FastPrep Practice Rules for the Source-Omitted Edge Cases
MERGE_ACCOUNTS <timestamp> <survivorId> <absorbedId> returns "false" if the IDs are equal or either account is not active. Otherwise add the absorbed balance and transaction total to the survivor, close the absorbed account at this timestamp, and return "true".
Retarget a pending transfer that names the absorbed account to the survivor. If that would make the pending transfer's source and target equal, cancel it and refund its held amount before combining balances.
GET_BALANCE <timestamp> <accountId> <timeAt> returns the account's balance immediately after all activity at timeAt, or after the latest earlier activity if nothing happened exactly then. Return the empty string if the account did not yet exist or was already closed at timeAt.
A merged-away account remains queryable for times before its merge timestamp. The survivor's earlier history remains unchanged; at the merge timestamp its balance becomes the combined balance.

Function
bankingAcceptedTransfersLevel4(queries: String[][]) → String[]

Examples
Example 1
queries = [["CREATE_ACCOUNT","1","account1"],["CREATE_ACCOUNT","2","account1"],["CREATE_ACCOUNT","3","account2"],["DEPOSIT","4","non-existing","2700"],["DEPOSIT","5","account1","2700"],["PAY","6","non-existing","2700"],["PAY","7","account1","2701"],["PAY","8","account1","200"]]
return = ["true","false","true","","2700","","","2500"]
The duplicate account creation fails. Missing-account operations and an overdraw return empty strings. The final payment succeeds and leaves account1 with 2500.
Example 2
queries = [["CREATE_ACCOUNT","1","account1"],["CREATE_ACCOUNT","2","account2"],["CREATE_ACCOUNT","3","account3"],["DEPOSIT","4","account1","2000"],["DEPOSIT","5","account2","3000"],["DEPOSIT","6","account3","4000"],["TOP_ACTIVITY","7","3"],["PAY","8","account1","1500"],["PAY","9","account2","250"],["DEPOSIT","10","account3","250"],["TOP_ACTIVITY","11","3"]]
return = ["true","true","true","2000","3000","4000","account3(4000), account2(3000), account1(2000)","500","2750","4250","account3(4250), account1(3500), account2(3250)"]
The first ranking follows the three deposits. Payments count toward total activity even though they reduce balances, so the second ranking uses totals 4250, 3500, and 3250.
Example 3
queries = [["CREATE_ACCOUNT","1","account1"],["CREATE_ACCOUNT","2","account2"],["DEPOSIT","3","account1","2000"],["DEPOSIT","4","account2","3000"],["TRANSFER","5","account1","account2","5000"],["TRANSFER","16","account1","account2","1000"],["ACCEPT_TRANSFER","20","account1","transfer1"],["ACCEPT_TRANSFER","21","non-existing","transfer1"],["ACCEPT_TRANSFER","22","account1","transfer2"],["ACCEPT_TRANSFER","25","account2","transfer1"],["ACCEPT_TRANSFER","30","account2","transfer1"],["TRANSFER","40","account1","account2","1000"],["ACCEPT_TRANSFER","86400045","account2","transfer2"],["TRANSFER","86400050","account1","account1","1000"]]
return = ["true","true","2000","3000","","transfer1","false","false","false","true","false","transfer2","false",""]
The first transfer fails for insufficient funds. Only account2 can accept transfer1, and it can be accepted only once. transfer2 expires before timestamp 86400045, so its held funds are refunded and acceptance fails.
Example 4
queries = [["CREATE_ACCOUNT","1","a"],["CREATE_ACCOUNT","2","b"],["DEPOSIT","3","a","100"],["DEPOSIT","4","b","50"],["TRANSFER","5","b","a","20"],["MERGE_ACCOUNTS","6","a","b"],["GET_BALANCE","7","b","4"],["GET_BALANCE","8","b","5"],["GET_BALANCE","9","b","6"],["GET_BALANCE","10","a","6"],["TOP_ACTIVITY","11","2"]]
return = ["true","true","100","50","transfer1","true","50","30","","150","a(150)"]
The pending transfer would become internal after the merge, so it is canceled and its held 20 is refunded before the balances combine. Account b keeps its historical balances before timestamp 6 but is closed at the merge timestamp.

Constraints
1 <= queries.length <= 500.
Every timestamp is a unique integer from 1 through 10^9, and queries are supplied in strictly increasing timestamp order.
Every query row is well formed and uses an operation available at this part.
Amounts and n are positive base-10 integers. Every balance and transaction total fits in a signed 64-bit integer.
Account identifiers are non-empty strings that do not contain commas or parentheses.

Reported by candidates. Source: FastPrep

Pattern and pitfall

The pattern is design with hash maps. Keep a map of accounts (balance, total transaction value, active flag) and a map of transfers (source, target, amount, created time, status). The edge case that breaks naive solutions is merging while transfers are pending. Any pending transfer naming the absorbed account must be retargeted to the survivor, and if that makes source equal target, you have to resolve it per the spec instead of leaving a self-transfer behind. Expiry is the second trap: acceptance at exactly created + 86400000 is still valid, and expired holds refund the source. Process expirations lazily at the start of every query, before the operation runs. Third trap: TOP_ACTIVITY sorts by value descending, then accountId ascending, and closed accounts must not appear. If you freeze on the merge rules during the live OA, StealthCoder is the hedge that reads the full spec and gives you working code.

Drill it cold or hedge it with StealthCoder. Either way, don't walk into the OA hoping you remember the trick.

If this hits your live OA

You can drill Banking System with Accepted Transfers, Part 4: Account Merging and Balance History cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Made for the candidate who got the OA invite this morning and has 72 hours, not six months.

Get StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Ramp's OA.

Ramp reuses patterns across OAs. Made for the candidate who got the OA invite this morning and has 72 hours, not six months. Works on HackerRank, CodeSignal, CoderPad, and Karat.

Banking System with Accepted Transfers, Part 4: Account Merging and Balance History FAQ

What's the trick in the Ramp banking system Part 4?+

Merging is easy. The trap is pending transfers that reference the absorbed account. Retarget them to the survivor, and handle the case where source and target become equal. Also make sure the absorbed account is closed so it's excluded from TOP_ACTIVITY and rejects later operations.

How should I handle transfer expiration?+

Process expirations lazily at the start of every query. A transfer expires after 86400000 ms, but acceptance at the exact expiration timestamp is still valid. Only refund the source once the timestamp is strictly past that point, and mark the transfer so it can't be accepted later.

Do failed transfers use up transfer IDs?+

No. Only valid transfers get the next global ID like transfer1, transfer2. Failures return the empty string and don't increment the counter. Check source equals target, missing accounts, and insufficient funds before you touch the counter.

When does an account's transaction value change?+

Deposits and payments count immediately. Transfers count for both source and target only after acceptance, not at initiation. Merging adds the absorbed account's total to the survivor. TOP_ACTIVITY then sorts by total descending and accountId ascending.

How do I prepare for this in 48 hours?+

Write the full class from scratch once, with levels 1 to 3 working. Then add merge and test it with pending transfers, expiry boundaries, and ties in TOP_ACTIVITY. Design questions reward clean state handling, so write small tests for each edge case instead of reading more theory.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Ramp.

OA at Ramp?
Invisible during screen share
Get it