HackTheRounds Interview Experiences
Stripe Software Engineer Intern Interview Experience (2026) - PaymentLedger OOP & Integration Round, Offer
Stripe 2026 Summer SWE Intern VO: two rounds covering a PaymentLedger OOP design with refund follow ups and a 60 minute Integration round cloning a Git repo and
By Anonymous · 2026-04-13
Background
Rising senior at a state school, zero FAANG internships on my resume going into this cycle. Stripe was the summer 2026 target I actually cared about, and the SWE intern loop turned out to be shorter than the new grad version: just two rounds after the OA. The whole thing took under four weeks end to end. I am writing this up mostly because the Integration round caught me completely off guard and I want the next round of interns to be better prepared than I was.
Timeline
- Online application: late February
- OA received: 8 days later, 5-day window
- OA submitted: 3 days after receiving
- VO invite: 11 days after OA submission
- VO1 (Coding, 45 min): 1 week after invite
- VO2 (Integration, 60 min): 2 days after VO1
- Offer: 4 business days after VO2
Total: about 4 weeks.
Virtual Onsite (2 rounds)
The intern loop at Stripe skips system design and the hiring-manager round. It is two technical sessions, 45 and 60 minutes, both on CoderPad with video on Zoom.
Round 1: Coding — PaymentLedger OOP Design (45 min)
Problem: Build a PaymentLedger class that tracks payments and refunds. The required methods are add payment(payment id, amount, timestamp) , add refund(payment id, amount, timestamp) , get total revenue() , and get payments by date(date) . Duplicate payment id values must be rejected. Refunds reduce the stored revenue.
The gotcha is that this is not algorithmically hard. It is an OOP design check. The interviewer cares whether you pick reasonable data structures, name methods consistently, and handle the edge cases without being prompted.
My structure was a dict keyed on payment id pointing to a small Payment dataclass with amount, timestamp, and a cumulative refund field. A separate defaultdict(list) keyed on date string pointed at the payment IDs for that date, so get payments by date was an O(k) lookup for k matching records. Total revenue was maintained as a running counter updated inside add payment and add refund rather than recomputed every query.
The follow-ups came steady:
- How do you support partial refunds? Answer: the refund method checks that `amount_refunded_total + new_refund <= original_payment_amount` and rejects the over-refund. I added a `refunded_cents` field to the Payment dataclass and updated it inline.
- If the record set grows huge, how do you speed up `get_payments_by_date`? Answer: the existing per-date index is already O(k). For extreme scale, bucket at the month level first and scan within the bucket. If you need range queries, a sorted structure like a TreeMap on timestamps is the right tool.
- What if the caller passes a malformed timestamp? Answer: validate on ingest with `datetime.fromisoformat` and raise a `ValueError` with a clear message. Do not store invalid data and hope the query side catches it.
- How would you persist this to a database? Answer: two tables, one for payments and one for refunds, with a unique index on `payment_id`. The refund table has a foreign key to the payment row and a `cumulative_refund_cents` denormalized value we update transactionally.
The interviewer spent the last five minutes on error handling. I had wrapped the core path in explicit duplicate detection and the refund math in bounds checks, which he noted positively.
[[problem/10?company=13|Design Payment Ledger]]
Round 2: Integration — Clone, Read, Extend (60 min)
This is the round nobody warned me about. I was handed a link to a small Git repo with a README, a partially implemented payment sync job, and failing unit tests. The task was to make the tests pass and extend the job with webhook handling.
The setup alone took 10 minutes. I had to clone, install dependencies, read the project layout, find the entry point, and run the existing tests to see what was failing. Stripe explicitly grades on how fast you bootstrap in an unfamiliar repo. Practice cloning random GitHub repos and getting their tests green without reading the full source first.
The actual coding breaks into three tasks:
- Call an external payments API with the provided client class to pull the day's transactions.
- Handle webhook callbacks that arrive out of order and update transaction status accordingly.
- Sync transaction status to a local DB, idempotently. Write unit tests for the sync path.
I worked top to bottom. The API fetch was straightforward because the client was already instantiated in conftest.py with mock credentials. The webhook handler needed a small state machine: pending can go to completed or failed , but once completed it can only go to reversed . I wrote the state machine as a dict of allowed transitions and validated every update against it.
The sync step is where idempotency mattered. The existing code was using INSERT , which would duplicate on retry. I changed it to an upsert keyed on transaction id and added a short unit test proving that running the sync job twice on the same payload produced a single DB row.
The last 10 minutes were me writing two more tests: one for an out-of-order webhook arriving after the sync had already completed the transaction, and one for the bounds check on partial refunds. The interviewer thanked me, reminded me to commit the changes, and said they were happy with the progress.
Result
Offer came back four business days after VO2. I was on a walk when the recruiter called. Took me a full minute to process what he said.
Tips
- Practice reading unfamiliar codebases cold. Clone five random medium-sized Python projects on GitHub and force yourself to run their tests within ten minutes. The Integration round is won or lost on your bootstrap speed, not on how fancy your final code is.
- Learn Stripe's idempotency vocabulary before the loop. Words like `idempotency_key`, `upsert`, `state machine`, and `out-of-order event` should roll off your tongue. The interviewer wants to hear them. Read Stripe's public API docs for an hour; it is the highest-ROI prep you can do for the intern loop.
- In the OOP round, maintain running totals. Do not recompute `get_total_revenue` by summing every payment on every call. Keep the total as a field and update it in the mutators. This is the cheapest signal of systems thinking.
- Validate inputs at the boundary, not in the core. Timestamp parsing, ID format, amount positivity: all of these should fail loudly on ingest. If your core path assumes valid data, you have pushed error handling into the wrong layer.
- Write tests during the interview, not after. In Round 2 I wrote the upsert test before I wrote the upsert code, which made the fix faster and scored me engineering-hygiene points. Stripe interns often forget that tests are part of the grading surface.
- Turn on commits early in Round 2. The interviewer explicitly asked me to `git commit` my changes. If you are a developer who lives on `git add` without committing until the end, retrain yourself for this round. Small, labeled commits are the easiest way to signal professional workflow.