Affirm

Affirm Software Engineer Interview Questions

22+ questions from real Affirm Software Engineer interviews, reported by candidates.

22
Questions
6
Round Types
8
Topic Areas
2024-2026
Year Range

Round Types

Phone 15 Phone Screen 3 Recruiter 1 Onsite 1 Manager 1 Coding 1

Top Topics

Questions

Just one question, but it's very detailed. The following content requires a score higher than 188. You can already view it. Hit counter. Similar to the one on LC. Please give me some points. Thank you

I can't go into too much detail about the NDA I signed, but here's a brief timeline: September 3rd: HR Reachout September 8th: Tech Phone Screen (original question on hack2hire) I was away for a week,

LeetCode #380: Insert Delete GetRandom O(1). Difficulty: Medium. Topics: Array, Hash Table, Math, Design, Randomized. Asked at Affirm in the last 6 months.

Affirm interview

Phone Screen 2026

Hey everyone! I have my first technical interview with Affirm in sometime and I’m feeling a mix of excitement and nerves. After the behavioral phone screen, they sent me an email stating it would 60 m

Hello! I\'d like to talk about my recent interview experience at X Company, which lasted only a week. When I joined as an SDE 1 last year, I had no idea...

I appeared for affirm technical interview, live coding test. I had practiced enough on leetcode that solving the question in test was quite straight forward. They did NOT select me...

/ Affirm | Phone Screen | Letters appearing most number of words Given an input list of strings, for each letter appearing anywhere in the list, find the other letter(s) that appear...

## Problem Design a class hierarchy for a simple turn-based card game. A standard deck has 52 cards (4 suits, 13 ranks). Players are dealt a hand. On each turn, a player plays one card; the highest-rank card wins the trick. Implement: ```python class Card: suit: str # HEARTS, DIAMONDS, CLUBS, SPADES rank: int # 2-14 (14 = Ace) class Deck: def __init__(self): ... # 52 cards def shuffle(self, seed: int): ... def deal(self, n: int) -> list[Card]: ... class Hand: def play(self, card: Card): ... def has_card(self, card: Card) -> bool: ... class Game: def __init__(self, num_players: int): ... def play_round(self) -> str: # returns winner player id ``` ## Follow-ups 1. How do you enforce that `deal` raises an error when the deck runs out of cards? 2. How would you add a `Trump` suit that beats all other suits regardless of rank? 3. Describe how you would serialize mid-game state to JSON for a save/resume feature. 4. How do you handle games with wildcards (jokers) that can substitute for any card?

## Problem A decision tree is stored as a list of nodes. Each node has: `feature_index` (int or None for leaf), `threshold` (float or None), `left` (child index), `right` (child index), `value` (class label, set only on leaves). Given this structure and a feature vector, implement `predict`. ```python class TreeNode: feature_index: int | None threshold: float | None left: int | None right: int | None value: int | None # only on leaf def predict(nodes: list[TreeNode], features: list[float]) -> int: # traverse from node 0; go left if features[node.feature_index] <= threshold ``` ## Example ``` # Tree: if x[0] <= 3.5 -> predict 0, else if x[1] <= 7.0 -> predict 1, else predict 2 features = [5.0, 8.0] Output: 2 ``` ## Follow-ups 1. How would you implement `predict_proba` that returns class probabilities instead of a hard label? 2. How do you handle missing feature values at inference time? 3. Extend to a random forest: given `n` trees, return the majority vote class. 4. Describe how you would serialize and load this tree from a JSON file.

## Problem A payment dispute follows this lifecycle: ``` OPENED -> EVIDENCE_SUBMITTED -> UNDER_REVIEW -> RESOLVED_WON -> RESOLVED_LOST -> WITHDRAWN ``` Invalid transitions (e.g., UNDER_REVIEW -> OPENED) must raise an error. Implement: ```python class Dispute: def __init__(self, dispute_id: str): self.status = "OPENED" def transition(self, new_status: str) -> None: # raises ValueError if transition is invalid def is_resolved(self) -> bool: def history(self) -> list[str]: # all statuses in order ``` ## Example ``` d = Dispute("d123") d.transition("EVIDENCE_SUBMITTED") d.transition("UNDER_REVIEW") d.transition("RESOLVED_WON") d.history() -> ["OPENED","EVIDENCE_SUBMITTED","UNDER_REVIEW","RESOLVED_WON"] d.transition("WITHDRAWN") # raises ValueError ``` ## Follow-ups 1. How would you represent the valid transitions as a data structure rather than hard-coded conditionals? 2. Add timestamps to each transition and a method `time_in_state(status)` that returns the duration. 3. How would you persist dispute state to a database and handle concurrent updates? 4. Extend to support automatic escalation: if `UNDER_REVIEW` for more than 30 days, auto-transition to `RESOLVED_LOST`.

## Problem Implement a simplified in-memory DOM tree. Each node has a `tag`, optional `id`, a list of `classes`, and child nodes. ```python class DOMNode: def __init__(self, tag: str, id: str = "", classes: list[str] = []): ... def append_child(self, child: 'DOMNode') -> None: ... def query_selector(self, selector: str) -> 'DOMNode | None': # supports: "tag", "#id", ".class", "tag.class" def query_selector_all(self, selector: str) -> list['DOMNode']: def remove_child(self, child: 'DOMNode') -> bool: ``` ## Example ```javascript // equivalent structure: // <div id="root"> // <p class="intro">...</p> // <span class="intro highlight">...</span> // </div> root.query_selector(".intro") // -> first <p> root.query_selector_all(".intro") // -> [<p>, <span>] root.query_selector("#root") // -> root div ``` ## Follow-ups 1. How would you support a descendant combinator, e.g., `"div p"` (all `<p>` inside `<div>`)? 2. Implement `innerHTML` that serializes the subtree back to an HTML string. 3. What traversal strategy does `query_selector` use? Why BFS vs. DFS matters here. 4. How do you handle `query_selector` on a very deep tree (10K levels) without stack overflow?

## Problem You are given a string representing a custom config file. The format uses indentation (2 spaces per level) to represent nesting, and `key: value` pairs. Values may be strings or nested sections. Parse it into a nested Python dict. ```python def parse_config(text: str) -> dict: ``` ## Example ``` text = """ database: host: localhost port: 5432 credentials: user: admin password: secret server: port: 8080 """ Output: { "database": { "host": "localhost", "port": "5432", "credentials": {"user": "admin", "password": "secret"} }, "server": {"port": "8080"} } ``` ## Follow-ups 1. How do you handle tabs vs. spaces, or mixed indentation? 2. What if values can be lists (lines starting with `- `)? 3. Write a `serialize(d: dict) -> str` function that is the inverse of `parse_config`. 4. How would you add line-number error reporting for malformed input?

## Problem Design a data structure that supports insert, delete, and getRandom in O(1) average time. ## Likely LeetCode equivalent LC 380 - Insert Delete GetRandom O(1) ## Tags Coding, hash_table, arrays, randomization, phone

## Problem Design a loan management system. A loan has a principal, annual interest rate, and term in months. Payments reduce the outstanding balance. ```python class Loan: def __init__(self, principal: float, annual_rate: float, term_months: int): ... def monthly_payment(self) -> float: # fixed amortized payment def make_payment(self, amount: float, month: int) -> None: def outstanding_balance(self, after_month: int) -> float: def is_paid_off(self) -> bool: def payment_schedule(self) -> list[dict]: # [{month, payment, principal_paid, interest_paid, balance}] ``` Use the standard amortization formula: `M = P * r*(1+r)^n / ((1+r)^n - 1)` where `r = annual_rate/12`. ## Example ``` loan = Loan(10000, 0.06, 12) # $10K at 6% for 12 months loan.monthly_payment() -> ~860.66 loan.outstanding_balance(after_month=6) -> ~5136.98 ``` ## Follow-ups 1. How does an early payoff (larger payment) affect the remaining schedule? 2. Add a `LoanPortfolio` class that manages multiple loans and reports total outstanding debt. 3. How would you handle variable interest rates (rate changes per quarter)? 4. Design the database schema to store loan state and payment history.

## Problem You receive a list of raw order log lines. Each line has format: ``` "<timestamp_iso> ORDER_<event_type> order_id=<id> [key=value ...]" ``` Event types: `CREATED`, `PAID`, `SHIPPED`, `DELIVERED`, `CANCELLED`. Parse the logs and produce a summary: ```python def summarize_orders( log_lines: list[str] ) -> dict[str, dict]: # returns {order_id: {"status": str, "events": list, "duration_minutes": int | None}} # duration = minutes from CREATED to DELIVERED (None if not delivered) ``` ## Example ``` logs = [ "2024-01-10T10:00:00 ORDER_CREATED order_id=X1 amount=50.0", "2024-01-10T10:05:00 ORDER_PAID order_id=X1 method=card", "2024-01-10T10:30:00 ORDER_DELIVERED order_id=X1", ] Output: {"X1": {"status": "DELIVERED", "events": [...], "duration_minutes": 30}} ``` ## Follow-ups 1. How do you handle log lines that arrive out of order by timestamp? 2. What if `order_id` appears in `CREATED` for the same ID more than once (duplicate events)? 3. Write a SQL version: given a table `order_events(order_id, event_type, event_time)`, compute the same summary. 4. Identify orders that took more than 2 hours from CREATED to DELIVERED.

## Problem Design a `PermanentStack` class that behaves like a standard stack but never truly destroys data. When you `pop()` an element, it is moved to an internal "archive" rather than discarded. All previously popped elements must remain retrievable. ```python class PermanentStack: def push(self, val: int) -> None: ... def pop(self) -> int: ... # removes from active, archives it def peek(self) -> int: ... # top of active stack def restore(self) -> int: ... # moves most-recently archived back to active def get_archive(self) -> list[int]: ... # all archived values, oldest first ``` **Example:** ``` push(1), push(2), push(3) pop() -> 3 (archive: [3]) pop() -> 2 (archive: [3, 2]) restore() -> 2 (active top: 2, archive: [3]) get_archive() -> [3] ``` ## Follow-ups - What if `restore()` must reinstate elements in LIFO order from the archive? - How would you implement `undo_all()` to move every archived element back in original push order? - What are the time and space complexities of each operation? - How would you persist this structure to disk so it survives a process restart?

## Problem Given a list of purchase events for a single user, each as `(item_id, timestamp)`, determine the most frequent consecutive purchase pattern of length `k`. A pattern is a sequence of exactly `k` distinct items bought in consecutive transactions (no gaps). Return the pattern as a list of item IDs in the order they were purchased. If there is a tie, return the lexicographically smallest sequence. ```python def most_frequent_pattern(purchases: list[tuple[int, int]], k: int) -> list[int]: ... ``` **Example:** ``` purchases = [(1,100),(2,200),(3,300),(1,400),(2,500),(3,600),(1,700),(4,800)] k = 2 Output: [1, 2] # (1->2) appears twice; (2->3) appears twice -> tie -> lex smaller ``` **Constraints:** `1 <= k <= len(purchases)`, timestamps are strictly increasing. ## Follow-ups - How does your approach change if the same item can appear multiple times within a pattern? - Extend to find patterns across multiple users and return patterns seen by at least `m` users. - What is the time complexity? Can you do it in O(n) for k=2?

## Problem Compress a string by replacing consecutive repeated characters with the character followed by its count. ## Likely LeetCode equivalent LC 443 - String Compression ## Tags Coding, strings, two_pointers, phone

## Problem Two strings are "fingerprint-equivalent" if one can be obtained from the other by consistently renaming characters. That is, there exists a bijection between the character sets such that applying it to every position of string A produces string B. Given a list of strings, group them by fingerprint equivalence class and return the groups. ```python def group_by_fingerprint(words: list[str]) -> list[list[str]]: ... ``` **Example:** ``` Input: ["aab", "xxy", "abc", "xyz", "bba"] Output: [["aab", "xxy", "bba"], ["abc", "xyz"]] ``` Explanation: "aab" -> normalize as 0,0,1; "xxy" -> 0,0,1; "bba" -> 0,0,1. "abc" -> 0,1,2; "xyz" -> 0,1,2. ## Approach Normalize each string by mapping each first-seen character to an incrementing integer. Use the normalized tuple as a hash key. ## Follow-ups - Does your fingerprint function handle strings with overlapping character ranges across inputs? - How would you extend this to work on sequences of integers rather than characters? - What if two strings of different lengths should never be in the same group -- is that guaranteed, and where in your code does that hold?

## Problem Design a `TransactionLog` system that records financial transactions and supports querying. ```python class TransactionLog: def record(self, txn_id: str, account: str, amount: float, ts: int) -> None: ... def get_balance(self, account: str, as_of: int) -> float: ... def get_transactions(self, account: str, start: int, end: int) -> list[dict]: ... def rollback(self, txn_id: str) -> bool: ... ``` - `record`: append a new transaction (positive = credit, negative = debit). - `get_balance`: sum all transactions for `account` with `ts <= as_of`. - `get_transactions`: return all transactions for `account` in time range `[start, end]`, sorted by ts ascending. - `rollback`: mark a transaction as voided; it no longer affects balance or appears in queries. Return False if txn_id not found. **Example:** ``` record("t1", "alice", 500.0, 1) record("t2", "alice", -200.0, 2) get_balance("alice", 2) -> 300.0 rollback("t2") get_balance("alice", 2) -> 500.0 ``` ## Follow-ups - How would you support multi-account transfers atomically? - What index structure gives O(log n) point-in-time balance queries? - How do you handle concurrent `record` and `rollback` calls safely?

See All 22 Affirm Software Engineer Questions

Full question text, answer context, and frequency data for subscribers.

Get Access