Among fintech companies, Stripe's OA style is the most distinctive—no LeetCode Hard, but 15-20 hidden test cases per problem, so "right idea, wrong code" is the most common failure mode. This article aggregates the latest 1point3acres reports from Stripe 2026 spring OA (Q1-Q2), ranked by frequency, with three real problems, complete Python solutions, and the grading system's deduction details. All problems are from real candidate reports between January and April 2026—completely different from older brace expansion / shipping cost problems.
Stripe 2026 Spring OA Overview
| Dimension | Details |
|---|---|
| Platform | HackerRank for Work |
| Total time | 120 minutes |
| Problems | Typically 1 large problem split into 4 Parts |
| Difficulty | Each Part is LeetCode Easy ~ Medium, escalating |
| Hidden tests | 15-20 total, 5-7 per Part |
| Pass line | 80%+ tests passed |
Problem 1: Payment Reconciliation (Highest Frequency)
Part 1: Basic Reconciliation
Statement: Given two data sources bank_transactions and internal_records, each row (transaction_id, amount, timestamp). Return transactions only in internal records and not confirmed by the bank.
def find_unreconciled(bank_transactions, internal_records):
"""
bank_transactions: List[Tuple[str, int, int]]
internal_records: List[Tuple[str, int, int]]
return: List[Tuple[str, int, int]], sorted by timestamp ascending
"""
bank_ids = {tx[0] for tx in bank_transactions}
unreconciled = [r for r in internal_records if r[0] not in bank_ids]
unreconciled.sort(key=lambda x: x[2])
return unreconciled
Time complexity: O(n + m log m).
Part 2: Fuzzy Match on Amount + Timestamp
Statement: From Part 1's unreconciled list, find pairs where amount matches and timestamps differ ≤ 60s—possibly the same transaction with a typo in the ID.
from collections import defaultdict
def find_fuzzy_matches(unreconciled, bank_transactions, time_tolerance=60):
bank_by_amount = defaultdict(list)
for tx in bank_transactions:
bank_by_amount[tx[1]].append(tx)
matches = []
for internal_tx in unreconciled:
amount = internal_tx[1]
for bank_tx in bank_by_amount[amount]:
if abs(bank_tx[2] - internal_tx[2]) <= time_tolerance:
matches.append((internal_tx, bank_tx))
break
return matches
Stripe deductions:
- No hashmap, scanning all bank_tx per internal_tx → O(n×m) TLE
- Missing
breakcauses duplicate matches - Not handling negative amounts (refunds)
Part 3: Conflict Resolution
Statement: When multiple internal transactions could match one bank transaction, assign by closest timestamp.
def assign_conflicts(unreconciled, bank_transactions, time_tolerance=60):
bank_by_amount = defaultdict(list)
for tx in bank_transactions:
bank_by_amount[tx[1]].append(tx)
used_bank = set()
result = []
unreconciled_sorted = sorted(unreconciled, key=lambda x: x[2])
for internal_tx in unreconciled_sorted:
amount = internal_tx[1]
best_match = None
best_diff = time_tolerance + 1
for bank_tx in bank_by_amount[amount]:
if bank_tx[0] in used_bank:
continue
diff = abs(bank_tx[2] - internal_tx[2])
if diff < best_diff:
best_diff = diff
best_match = bank_tx
if best_match:
used_bank.add(best_match[0])
result.append((internal_tx, best_match))
return result
Part 4: Streaming Reconciliation (State Reset)
The hardest part: process streaming inputs in real time, match each new transaction immediately or hold it, and after 5 minutes unmatched, escalate for manual review.
from collections import deque
class StreamReconciler:
def __init__(self, time_tolerance=60, escalation_timeout=300):
self.time_tolerance = time_tolerance
self.escalation_timeout = escalation_timeout
self.pending_internal = deque()
self.pending_bank = deque()
self.matched = []
self.escalated = []
def process(self, tx_type, tx):
if tx_type == "internal":
self.pending_internal.append(tx)
else:
self.pending_bank.append(tx)
self._try_match(tx[2])
self._escalate_old(tx[2])
def _try_match(self, current_time):
for internal_tx in list(self.pending_internal):
for bank_tx in list(self.pending_bank):
if (internal_tx[1] == bank_tx[1] and
abs(internal_tx[2] - bank_tx[2]) <= self.time_tolerance):
self.matched.append((internal_tx, bank_tx))
self.pending_internal.remove(internal_tx)
self.pending_bank.remove(bank_tx)
return self._try_match(current_time)
def _escalate_old(self, current_time):
while self.pending_internal and current_time - self.pending_internal[0][2] > self.escalation_timeout:
self.escalated.append(self.pending_internal.popleft())
while self.pending_bank and current_time - self.pending_bank[0][2] > self.escalation_timeout:
self.escalated.append(self.pending_bank.popleft())
Problem 2: Webhook Retry System (30% Frequency)
Statement
Implement Stripe's webhook exponential backoff. After each failure, the next retry delay doubles (1s, 2s, 4s, 8s, ...), up to 8 attempts. Support:
dispatch(event_id, attempt_at): register a deliverymark_failed(event_id, failed_at): mark failed, auto-compute next retryget_due_events(now): return all events that should retry now
from collections import defaultdict
import heapq
class WebhookRetrySystem:
def __init__(self, max_attempts=8, base_delay=1):
self.max_attempts = max_attempts
self.base_delay = base_delay
self.attempts = defaultdict(int)
self.due_heap = []
self.failed_permanently = []
def dispatch(self, event_id, attempt_at):
self.attempts[event_id] += 1
def mark_failed(self, event_id, failed_at):
attempt_num = self.attempts[event_id]
if attempt_num >= self.max_attempts:
self.failed_permanently.append(event_id)
return
delay = self.base_delay * (2 ** (attempt_num - 1))
retry_time = failed_at + delay
heapq.heappush(self.due_heap, (retry_time, event_id))
def get_due_events(self, now):
due = []
while self.due_heap and self.due_heap[0][0] <= now:
_, event_id = heapq.heappop(self.due_heap)
due.append(event_id)
return due
Stripe deductions:
- No heap, full scan on
get_due_events→ TLE on large input 2 ** attempt_numinstead of2 ** (attempt_num - 1)makes first retry 2s instead of 1s- Missing max_attempts boundary
Problem 3: Currency Conversion Graph (25% Frequency)
Statement
Given exchange rates rates = [(from, to, rate), ...], implement convert(from_currency, to_currency, amount). If no direct rate, find an intermediate conversion path.
from collections import defaultdict, deque
class CurrencyConverter:
def __init__(self, rates):
self.graph = defaultdict(dict)
for from_cur, to_cur, rate in rates:
self.graph[from_cur][to_cur] = rate
self.graph[to_cur][from_cur] = 1.0 / rate
def convert(self, from_cur, to_cur, amount):
if from_cur == to_cur:
return amount
if from_cur not in self.graph:
return None
queue = deque([(from_cur, amount)])
visited = {from_cur}
while queue:
current_cur, current_amount = queue.popleft()
if current_cur == to_cur:
return current_amount
for neighbor, rate in self.graph[current_cur].items():
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, current_amount * rate))
return None
Test:
rates = [
("USD", "EUR", 0.9),
("EUR", "GBP", 0.85),
("GBP", "JPY", 180),
]
conv = CurrencyConverter(rates)
print(conv.convert("USD", "JPY", 100)) # 100 * 0.9 * 0.85 * 180 = 13770.0
Stripe hidden tests:
- Cycles (A → B → A): BFS handles naturally
- Floating-point precision (USD→EUR→USD ≠ original): Stripe allows 5-decimal tolerance
- Unknown currency: return None, not throw
Stripe OA Strategy Summary
| Mistake | Deduction | Fix |
|---|---|---|
| No empty input handling | 5 pts/Part | First line: if not data: return [...] |
| No negative/zero handling | 3 pts/Part | Refunds are negative |
| Brute force O(n²) | 0 pts (TLE) | Use hashmap or heap |
| Abbreviated function names | 1 pt/method | bank_tx > bt |
| No unit tests | 5 pts total | Add at least 3 asserts |
FAQ
Do I need to finish all Parts?
Not necessarily. Stripe scores on absolute points + Part completion. Full credit on Parts 1+2 (~60%) still has ~30% pass rate. Prioritize 100% correctness on Parts 1-2 before attempting Part 3.
Can I Google during the OA?
Officially no (HackerRank records via webcam), but enforcement is loose. Looking up docs (e.g., heapq syntax) is fine—don't search algorithm solutions. Graders flag stylistic anomalies suggesting non-original code.
How fast is feedback?
Usually 5-10 business days. Stripe's HR cadence is stable, and rejections are explicit (friendlier than Google/Meta).
Do these problems match 1point3acres?
Yes—all problems come from 2026 Q1-Q2 Stripe board on 1point3acres. Variable names and Part splits vary slightly per candidate; Stripe's question bank rotates monthly.
Recommended language?
Python (first choice), JavaScript (second). Stripe internally uses Ruby, but the OA doesn't enforce it. Avoid C++: Stripe problems are string-heavy and C++ is slower to write.
Preparing for Stripe OA?
oavoservice maintains the Stripe problem bank (including older problems like brace expansion, shipping cost, subscription notifications not covered here), with timed OA mocks and hidden-test-case rehearsals. Coaches include former Stripe engineers familiar with every grading deduction.
Add WeChat: Coding0201 to get the Stripe OA problem pack.
#Stripe #OA #PaymentSystems #Webhook #RealQuestions #1point3acres
Contact
Email: [email protected]
Telegram: @OAVOProxy