
I took The Trade Desk's CodeSignal assessment not long ago. The assessment lasted 90 minutes in total, and each level’s specific coding section could only be unlocked after passing the previous level. Below, I will share the questions for each level as well as my detailed problem-solving approach at the time.
I genuinely appreciate how much Linkjob.ai has helped me throughout the process, which is why I’m sharing all of my OA details here. Having an invisible AI assistant running in the background really takes away a lot of stress.
Three fundamental functions (account creation, fund deposit, and fund transfer) needed to be implemented, with clear return values and core validation logic to ensure accurate data flow and no anomalies:
CreateAccount(timestamp: int, accountId: str) -> bool success: Creates a new account. Returns true if creation is successful, and false if the accountId already exists (to avoid duplicate accounts).
DepositMoney(timestamp: int, accountId: str, amount: int) -> int balance: Deposits a positive integer amount into the specified account. First verifies the existence of the account; after deposition, returns the updated balance.
TransferMoney(timestamp: int, source: str, target: str, amount: int) -> int balance: Transfers funds from the source account to the target account. Requires verification that both accounts exist and the source account has a balance ≥ the transfer amount. After the transfer, returns the updated balance of the source account (supplementing return value details). Meanwhile, the outgoing amount of the source account must be recorded (to reserve data for the "total expenditure statistics" in Level 2).
Data Structure Selection: Use a hash table (e.g., Python’s dict) as the core storage. The key is accountId, and the value is a dictionary containing multiple fields. Example structure:
accounts = {
"account123": {
"balance": 0, # Current balance
"total_outgoing": 0, # Cumulative expenditure (transfer-out + subsequent withdrawals), used in Level 2
"transactions": [], # Transaction records (including timestamp, type, amount, etc.), used for merging in Level 4
"is_merged": False, # Flag indicating if the account has been merged, used for queries in Level 4
"merged_to": None # If merged, records the target account ID, used for queries in Level 4
}
}Boundary and Exception Handling:
Before all operations, verify if the account exists. If CreateAccount is called with an existing ID, return false; if Deposit or Transfer is called with a non-existent ID, throw an exception or return an error indicator (per interview agreement).
Amount validation: The amount for Deposit and Transfer must be a positive integer; otherwise, the operation is rejected.
Special transfer validation: The source and target accounts cannot be the same (to avoid invalid operations), and the source account’s balance must be ≥ the amount to prevent overdrafts.

On the basis of Level 1, add the WithdrawMoney interface and implement the "Top K accounts by total expenditure" function:
New Interface: WithdrawMoney(timestamp: int, accountId: str, amount: int) -> int balance: Withdraws funds from the specified account. Requires verification that the account exists and the balance ≥ the withdrawal amount. After withdrawal, returns the updated balance, and the withdrawal amount is included in "total expenditure".
Top K Query Rules:
Definition of total expenditure: The sum of the account’s Transfer outgoing amount and Withdraw amount (i.e., the accumulated value of the total_outgoing field).
Sorting logic: First sort by total expenditure in descending order; if amounts are equal, sort by accountId in ascending alphabetical order.
Return format: A list where each element is (accountId, totalSpend), e.g., [("acc001", 5000), ("acc002", 3000)] (supplementing a specific format example).
Cumulative Logic for Total Expenditure: When TransferMoney (for the source account) and WithdrawMoney are executed, synchronously update accounts[accountId]["total_outgoing"] to avoid traversing historical records during subsequent statistics (improving efficiency).
Efficient Calculation of Top K:
If the number of accounts is small (e.g., in the simplified scenario of an interview), directly traverse all unmerged accounts (excluding accounts with is_merged=True), collect (accountId, total_outgoing), sort them according to the rules, and take the top K.
For performance considerations, maintain a priority queue (min-heap) with a size of K. Dynamically replace the top element of the heap during traversal; the final elements in the heap are the Top K (note that when amounts are equal, the accountId must be included in the heap elements for comparison to handle ascending sorting).
Boundary Handling: Determine whether accounts with zero total expenditure should participate in sorting (per interview agreement, usually only accounts with expenditure are counted). If K is larger than the total number of accounts, return all eligible accounts.
I have to say, Linkjob AI is really easy to use. I used it during the interview after testing its undetectable feature with a friend beforehand. With just a click of the screenshot button, the AI provided detailed solution frameworks and complete code answers for the coding problems on my screen. I’ve successfully passed the test, with the HackerRank platform not detecting me at all.

Implement "scheduled payment" and "cancel payment" functions, while supporting queries on the execution status of scheduled payments. Core rules are supplemented as follows:
Scheduled Payment Interface: SchedulePayment(timestamp: int, accountId: str, amount: int, timeDelay: int) -> str paymentId:
Function: Automatically deduct the amount from the accountId at current timestamp + timeDelay (this amount must be included in the account’s "total expenditure").
paymentId Generation Rule: Share a global auto-increment counter, formatted as "payment$counter" (e.g., if the counter starts at 1, the paymentId for the first scheduled payment is "payment1").
Pre-verification: Confirm that the account exists and the amount is a positive integer; there is no need to verify the current balance (verification will be done when the payment is executed).
Cancel Payment Interface: CancelPayment(timestamp: int, accountId: str, paymentId: str) -> bool success:
Function: Cancel the specified scheduled payment. Cancellation is only allowed if "the payment has not been executed" (i.e., current timestamp < scheduled execution time).
Return Value: true indicates successful cancellation; false indicates failed cancellation (e.g., paymentId does not exist, account mismatch, payment has been executed or cancelled).
New Status Query Interface: GetStatus(timestamp: int, accountId: str, paymentId: str) -> str status:
Function: Query the status of the specified scheduled payment. Return values include "SCHEDULED" (scheduled but not executed), "EXECUTED" (executed), "CANCELLED" (cancelled), and "FAILED" (execution failed, e.g., insufficient balance).
Verification: Confirm that the paymentId belongs to the current accountId; otherwise, return an error status (e.g., "INVALID_ACCOUNT").
Storage Structure for Scheduled Payments: Add a hash table schedule_payments, where the key is paymentId and the value is a dictionary containing the following fields:
schedule_payments = {
"payment1": {
"accountId": "acc001",
"amount": 1000,
"scheduled_time": 1620000000, # timestamp + timeDelay
"status": "SCHEDULED", # Initial status
"create_timestamp": 1619999000 # Timestamp when the scheduled payment was created
}
}Meanwhile, maintain a global auto-increment counter payment_counter (initialized to 0). Each time SchedulePayment is called, counter += 1 to generate the paymentId.
Payment Execution Logic: Maintain a "pending payment list", sorted in ascending order of scheduled_time. Each time an operation (e.g., Deposit, Transfer) or query is performed, first check if there are payments in the list with scheduled_time ≤ current timestamp:
During execution, verify the account balance: If the balance ≥ amount, deduct the amount, update the account balance and total_outgoing, and change the status to "EXECUTED".
If the balance is insufficient, change the status to "FAILED" (no repeated execution to avoid repeated deductions).
Cancel Payment Logic: Query the record corresponding to paymentId in schedule_payments. Verify that the accountId matches, the status is "SCHEDULED", and the current timestamp < scheduled_time. If all conditions are met, change the status to "CANCELLED" and return true.
Implement the account merging function (retaining balances and transaction records) and support historical balance queries. Core rules are supplemented as follows:
Account Merging Interface: Merge(timestamp: int, source: str, target: str) -> bool success:
Function: Merge the source account (source account) into the target account (target account). After merging, the source account can no longer be operated (marked as merged).
Merged Content: Add the current balance of the source account to the target account’s balance, add the source account’s total_outgoing to the target account’s total_outgoing, and append the source account’s transaction records to the target account’s transaction records.
Pre-verification: Both source and target accounts exist, neither has been merged (to avoid repeated merging), and source ≠ target. If all conditions are met, return true.
Historical Balance Query Interface: GetBalance(int requestExecutionTime, string accountId, string queryTime) -> int balance:
Function: At the requestExecutionTime, query the balance of the accountId at queryTime (which needs to be converted to an integer timestamp).
Key Restriction: If accountId is a merged account (source account) and queryTime ≥ the timestamp of merging, return a null value or 0 (clarifying the rule that "querying historical balances after merging returns null"). If queryTime < merging time, query the historical balance of the source account before merging.
Data Processing for Account Merging:
During merging, update the balance, total_outgoing, and transactions of the target account (directly extend the transaction records of the source account).
Mark the source account with is_merged=True and merged_to=target. All subsequent operations (e.g., Deposit, Transfer) on the source account will return failure.
Handle pending scheduled payments of the source account: If there are paymentIds with status "SCHEDULED", choose to either cancel them automatically (change status to "CANCELLED") or transfer them to the target account (update the accountId of the payment). Select the option per interview agreement; cancellation is usually simpler.
Historical Balance Query Logic:
First, verify the account status: If accountId is a merged source account and queryTime ≥ merging timestamp, return null.
If the account is valid, traverse the transactions list of the account (transaction records must include fields such as timestamp, type, amount, and balance_after). Find the last transaction with timestamp ≤ queryTime; its balance_after is the balance at queryTime.
Optimization: Maintain "historical balance snapshots" in transaction records to avoid traversing all records during each query (e.g., save a snapshot every 10 transactions to reduce the number of traversals).
Before the interview, I also came across a set of highly valuable real interview questions. I'm attaching them here for reference.
Basic key-value DB (DB: {key: {field: value}}); all interfaces take timestamp (no clear use in Level 1).
set(timestamp: int, key: str, field: str, value: int)
Set value for field under key; create key if missing, overwrite field if exists.
get(timestamp: int, key: str, field: str) -> int | None
Query field value under key; return None if key/field missing.
compare_and_set(timestamp: int, key: str, field: str, expected_value: int, new_value: int) -> bool
Update to new_value if field == expected_value (return True); else False (key/field missing or mismatch).
compare_and_delete(timestamp: int, key: str, field: str, expected_value: int) -> bool
Delete field if value == expected_value (return True); else False (key/field missing or mismatch).
Add full scan by key and prefix-filtered scan (based on Level 1).
scan(timestamp: int, key: str) -> list[str]
Return [field(value)] for all fields under key; empty list if key missing.
scan_with_prefix(timestamp: int, key: str, prefix: str) -> list[str]
Return [field(value)] (strings start with prefix) under key; empty list if key missing/no match.
Add TTL-enabled set interfaces; adapt existing interfaces to ignore expired fields.
Data Structure
Upgrade to DB: {key: {field: {value: int, expire_at: int}}} (expire_at = timestamp + ttl; ttl=0 → never expire).
New Interfaces
set_with_ttl(timestamp: int, key: str, field: str, value: int, ttl: int)
Set value + expire_at for field (same logic as set).
compare_and_set_with_ttl(timestamp: int, key: str, field: str, expected_value: int, new_value: int, ttl: int) -> bool
Same as compare_and_set; set expire_at if update succeeds.
Existing Interfaces Adaptation
For set/get/compare_and_set/compare_and_delete/scan/scan_with_prefix: if field exists and expire_at > 0 && timestamp > expire_at → treat as non-existent.
Add interface to query value valid at a specified historical time.
Data Structure
Add HistoryDB: {key: {field: list[{value: int, set_at: int, expire_at: int}]}} (record each field change; original DB = latest state cache).
New Interface
get_when(timestamp: int, key: str, field: str, at_timestamp: int) -> int | None
Traverse field’s history; filter latest record (set_at <= at_timestamp and (expire_at == 0 || expire_at > at_timestamp)).
Return value if record exists; else None.
Adaptation
All Level 1-3 write ops (set/compare_and_set/set_with_ttl/compare_and_set_with_ttl) sync to HistoryDB.
The rational use of hash tables (for account storage and scheduled payment storage), priority queues (for Top K), and lists (for transaction records and pending payments). It is necessary to explain the reasons for selection (e.g., hash tables support O(1) lookup and modification, making them suitable for high-frequency account operations).
Pre-verification (account existence, valid amount, valid status) and post-data synchronization (balance, total expenditure, transaction records) for each interface. Both "normal processes" and "abnormal scenarios" (e.g., insufficient balance for transfers, cancelling executed payments) need to be covered.
When adding functions from Level 1 to Level 4, it is necessary to ensure that new functions do not break existing logic (e.g., scheduled payments in Level 3 do not affect total expenditure statistics in Level 2, and merging in Level 4 does not affect historical queries). Data field design should reserve space for expansion (e.g., is_merged and merged_to fields).
Details such as the generation rule of paymentId, the implementation of alphabetical sorting for accountId (needing to call string comparison methods), and the handling of scheduled payments after merging. These details reflect the rigor of the code.
Before starting practice problems, I spent time familiarizing myself with the testing environment. I registered for a CodeSignal account and used its Practice Area. This helped me adapt to the IDE layout, keyboard shortcuts, and the process of running test cases.
I also conducted mock assessments. This was crucial for training myself to remain focused under real-time pressure. During these simulations, I strictly adhered to the test rules, avoiding external resources to eliminate test anxiety.
I knew the CodeSignal assessment usually consisted of four questions of varying difficulty, so my study was highly structured and focused on Medium-difficulty problems.
My study plan covered these key areas:
Arrays and Strings: I focused on two pointers, sliding window techniques, and hash maps, practicing problems involving substring searches, deduplication, and ordered array operations.
Linked Lists: I focused on fundamental operations like fast and slow pointers, reversal, and merging.
Trees and Graphs: I practiced Depth First Search (DFS) and Breadth First Search (BFS), covering traversal problems and connected components.
Efficiency Optimization: I emphasized binary search, dynamic programming, and greedy algorithms, focusing on problems where an O(N log N) or O(N) solution was necessary.
My practice method was disciplined. I did not just blindly solve problems; I dedicated two to three days to each data structure or algorithm type to ensure I thoroughly understood the underlying principles and optimal solution patterns. I also set strict time limits for myself during daily practice (e.g., 15-20 minutes for a medium problem) to train my ability to think and make quick decisions.
I understood that The Trade Desk highly valued code efficiency, readability, and engineering standards. I made sure my code met a high standard. I always analyzed the time and space complexity of the problem before writing any code. If my initial idea was O(N squared), I worked hard to find an O(N) or O(N log N) solution. My code was clean; I used clear variable and function names. Especially for the third, more extensive implementation question, I split complex logic into multiple functions with single responsibilities to ensure high readability and maintainability. I proactively considered and tested edge cases, including null inputs, invalid inputs, and boundary conditions, to ensure my code ran robustly.
If you’re referring to skipping a certain level, that’s not possible. The four levels of questions follow a sequential order. You can’t unlock subsequent levels without completing the previous ones. However, you can first check the requirements for the later levels to get an overall understanding before starting to write code.
Focus on low-latency systems: efficient key-value stores, LRU/LFU caches, binary search, and O(1)/O(log n) optimized algorithms for real-time data processing.
Sharing My Authentic Experience With xAI CodeSignal Assessment
My Journey to Success in Hudson River Trading Assessment
Successfully Overcoming the Meta CodeSignal Test Experience
Lessons Learned From My Visa CodeSignal Assessment Journey
My Insider Tips for Passing the Coinbase CodeSignal Assessment