Interview guides · BILL
BILL Software Engineer Interview Guide
BILL Software Engineer interview guide: the three reported rounds, coding, system design and behavioral questions, and a seven-day preparation plan.
The 60-second brief
BILL builds financial operations software for small and midsize businesses. Candidates describe engineering work on platforms that move payments, synchronise financial data between systems and provide the infrastructure underneath both. Reports name three areas a Software Engineer may work in: the Payments Platform, Infrastructure and Product features. In practice that points to services behind RESTful APIs, where a duplicated request, a rounding slip or a half-finished sync is a real defect, so correctness and reliability are the themes to prepare around.
This guide is for candidates interviewing for a Software Engineer role at BILL. Reports say system design comes up mainly for mid-level and senior positions, so those candidates should weight the design material more heavily. It covers the three reported rounds, how to answer the reported behavioral questions in depth, a seven-day plan across coding, design and behavioral practice, and the mistakes that cost candidates most on this kind of loop.
Candidates report three stages over roughly 3-5 weeks: an Initial Screening, a Technical Evaluation (a coding challenge or a deep-dive technical phone screen) and a Multi-Round Interview covering technical depth and behavioral fit, virtual or in person.
Focus areas
- Coding interviews (general)
- Data Structures
- Algorithms
Overview
Candidate reports describe a fundamentals-heavy loop. The coding material centres on arrays, linked lists, trees and hash maps, and on implementing sorting, searching and traversal. Reported scenarios include a variation on a classic algorithm such as Knapsack or a string manipulation task, debugging a snippet during a live session, and improving a brute-force solution to a better time complexity. Reported questions also ask you to explain time and space complexity, optimise search or retrieval over a dataset, and handle null values or unexpected input formats.
The design questions candidates report are about payment processing with high availability, data synchronisation across multiple platforms, monolith versus microservices, a notification system for millions of users and consistency in a distributed system. They fit a company whose reported work is payments and financial data sync. For each one, prepare the failure story as well as the happy path: what happens on a retry, on a duplicate message, when a downstream system is down, and how you would detect and repair divergence.
The behavioral questions candidates report are about a technical disagreement, a pivot caused by changing requirements, feedback from senior engineers or product managers, a tight deadline without losing code quality, and why fintech and BILL. Reports also describe the role as owning features from design to deployment, writing tests, reviewing code and working in squads with product managers, designers, QA and DevOps, so choose stories that show that kind of collaboration with evidence of what you personally decided.
The stack candidates describe asks for one modern language such as Java, Python or JavaScript/TypeScript, experience with RESTful APIs and strong data structures and algorithms. Cloud experience (AWS or GCP), Docker and Kubernetes, and fintech or payment-scale background are listed as nice to have. Do your practice in the language you know best, and be ready to explain how that language handles nulls, integer overflow, exceptions and recursion depth, because the edge-case and error-handling questions land there.
The BILL interview process
Candidates describe three stages. The names below are the ones reported, and the order is the one reported; the exact format varies by team and seniority.
Initial Screening
The Initial Screening is reported as a conversation to align on expectations and experience. No coding format is reported for this stage, so treat it as the place where your background, your reasons for applying and the shape of the rest of the process get settled. Approach it by having a tight account of your recent projects and a specific answer to why fintech and why BILL, then ask what the technical stage will look like so you can prepare for the right format.
What to demonstrate
- How your experience lines up with the Software Engineer role
- How clearly you describe projects you have owned
- Your motivation for fintech and for BILL
How to prepare
- Pick three projects from your resume and write down for each the problem, your own decision, the trade-off and the outcome, since candidates are told to expect detailed questions on anything listed.
- Write a specific answer to the question on why you are interested in the fintech space and BILL, using the payments, data sync and small-business focus candidates describe.
- Ask the recruiter whether the technical stage is a coding challenge or a phone screen, and which language you may use.
Technical Evaluation
The Technical Evaluation is reported as either a coding challenge or a deep-dive technical phone screen. The reported coding material covers arrays, linked lists, trees and hash maps, sorting, searching and traversal, plus defensive handling of bad input. Example scenarios candidates report are a Knapsack-style or string manipulation task, debugging a supplied snippet live, and optimising a brute-force solution. Approach it by clarifying the input, stating the brute-force cost, improving it, writing readable modular code and then giving time and space complexity and edge cases.
What to demonstrate
- Working code for data structure and algorithm problems
- Ability to state and improve time and space complexity
- Defensive handling of null values and unexpected input
- Reading and debugging unfamiliar code
How to prepare
- Solve string and array transformation problems in your chosen language, and finish each by stating complexity and listing empty, single-element, duplicate and null inputs.
- Practise a dynamic programming problem in the Knapsack family both top-down with memoisation and bottom-up, and say what the table dimensions are.
- Check your editor, screen sharing and audio before the session, and tell the recruiter at once if the platform fails.
Multi-Round Interview
The Multi-Round Interview is reported as several virtual or in-person sessions that cover both technical depth and behavioral fit. The reported design questions (payment processing availability, cross-platform data sync, monolith versus microservices, notifications at scale, distributed consistency) and the behavioral questions are the natural preparation material for a stage with that scope. Approach the technical sessions by fixing scope, naming trade-offs and walking through failure cases, and the behavioral ones with stories in which you made a visible decision.
What to demonstrate
- Technical depth beyond the first coding problem
- Reasoning about scalability, database choices and trade-offs in distributed systems
- Collaboration with product managers and other engineers
- Ability to explain technical choices to non-technical colleagues
How to prepare
- Build a one-page outline for the payment processing availability question covering idempotency keys, retries, a durable queue, replicas and failover, and how you would reconcile after an outage.
- Outline the data synchronisation question around change capture or polling, ordering, conflict resolution and replaying failed syncs.
- Prepare five behavioral stories, one each for disagreement, pivot, feedback, deadline versus quality and motivation, each with a result you can state.
- Prepare to explain a monolith versus microservices choice using a real system you worked on, including what you would not split.
Where candidates lose points
Giving a complexity answer that ignores space and recursion
When asked about time and space complexity, name the dominant operation, then count the extra memory, including the recursion stack and any memo table in a dynamic programming solution. Say how the answer changes for the brute-force version and for your improved one.
Coding only the happy path on the string, array or linked-list problem
Before writing, list empty input, a single element, duplicates, negatives, null and malformed formats, and handle them in the code, not only in conversation. Reports list defensive programming as an explicit topic.
Designing the payment or sync service without duplicate and failure handling
For the payment availability and data synchronisation questions, add idempotency keys, a retry policy with limits, a durable queue and a reconciliation step to your outline. State what happens when a downstream system is unavailable and how you detect divergence.
Telling behavioral stories in which the team acts and you do not
Write each story so that one decision is clearly yours, with the alternatives you rejected and a result you can state. Use the situation, task, action, result structure, but spend most of the time on the action and its trade-offs.
Giving a generic answer to why fintech and why BILL
Base the answer on the payments, financial data sync and small-business focus candidates describe, and connect it to one of your own projects. Prepare it before the first screening, where the question may come up.
Questions & practice
Can you explain the time and space complexity of your solution?
Medium · Technical Fundamentals and Algorithms
Approach
- Count the work per input element: one pass is O(n), a nested pass over the same input is O(n^2), a comparison sort is O(n log n), and a heap that keeps the top k of n items is O(n log k). Name every variable, for example O(n + m) for two inputs, instead of collapsing everything into n.
- Separate auxiliary space from output space, and count the recursion stack: a DFS on a balanced tree uses O(log n) stack, but on a skewed tree it uses O(n).
- State the assumptions behind each bound. Hash map lookups are O(1) on average but O(n) in the worst case under heavy collisions. A dynamic array append is amortized O(1). Quicksort is O(n log n) on average and O(n^2) in the worst case.
- Point out hidden costs: building a string by concatenation in a loop can be O(n^2) because Java and Python strings are immutable, slicing copies, and a list membership test is O(n). Then give the fix (StringBuilder, ''.join, a set).
Follow-up
- Can you bring the extra space down to O(1), and what does that cost in time?
- What happens to your bound if the hash map degrades or the input arrives already sorted?
- Which term dominates when k approaches n, and would you change the approach then?
Write a function to perform string manipulation or array transformation.
Medium · Technical Fundamentals and Algorithms
Approach
- Clarify the contract first: ASCII or Unicode, case sensitivity, whether the input can be null or empty, and whether to modify in place or return a new value.
- Name the pattern that fits. Two pointers handle reversing, palindrome checks and in-place dedupe of a sorted array in O(n) time and O(1) space. A sliding window with a hash map finds the longest substring without repeats in O(n). A frequency count (an int[26] or a map) settles anagram checks in O(n). Prefix sums answer range sums in O(1) after O(n) setup.
- Strings are immutable in Java and Python, so collect the output in a StringBuilder or a list and join once. Repeated concatenation can turn an O(n) loop into O(n^2).
- Before you call it done, walk through the edge cases: empty input, a single element, all duplicates, surrounding whitespace, negative numbers, and int overflow in Java when you sum or multiply.
Follow-up
- Can you do it in place with O(1) extra space?
- What breaks if the string contains multi-byte Unicode characters or emoji?
- How would you process input too large to hold in memory, for example a stream of lines?
Given a dataset, how would you optimize the search or retrieval process?
Medium · Technical Fundamentals and Algorithms
Approach
- Ask about the workload: dataset size, read-to-write ratio, query shape (exact key, range, prefix, top-k), and whether the data is static or changing.
- Pick the structure from the query. A hash map gives O(1) average exact lookup but no ranges. A sorted array with binary search gives O(log n) lookup and range scans but O(n) inserts. A balanced BST such as TreeMap gives O(log n) insert, lookup and range. A trie answers prefix queries in O(L) for a key of length L.
- If the data sits in a relational database, add a B-tree index on the filtered columns. In a composite index, put the equality columns before the range column, consider a covering index, and confirm the plan with EXPLAIN. Every extra index slows writes.
- For hot repeated reads, add a cache: an LRU built from a hash map plus a doubly linked list gives O(1) get and put. Decide how entries are invalidated when the underlying record changes.
Follow-up
- What changes if the dataset no longer fits in memory on one machine?
- The workload becomes write-heavy: which of these structures or indexes would you drop?
- How would you order a composite index for a query that filters on customer and a date range?
Can you implement a solution using dynamic programming or recursion?
Medium · Technical Fundamentals and Algorithms
Approach
- Show that the problem has overlapping subproblems and optimal substructure. Write the plain recursion first, add memoization (top-down), then convert to a bottom-up table if stack depth is a risk. CPython's default recursion limit is 1000.
- Define the state, the recurrence and the base case out loud. For 0/1 knapsack: dp[i][w] = max(dp[i-1][w], dp[i-1][w - wt_i] + val_i) when wt_i <= w. Time is O(nW), which is pseudo-polynomial in the capacity W.
- Reduce the space to O(W) with a 1-D array. For 0/1 knapsack, iterate w from W down to wt_i so each item is used at most once. Iterating upward gives the unbounded version instead.
- Know the neighbouring patterns: coin change (dp[a] = min over coins c of dp[a - c] + 1, returning -1 when a is unreachable), LCS and edit distance in O(mn), and string-splitting recursion with memoization.
- Test the edge cases: capacity 0, an item heavier than the capacity, an empty item list, and a target that cannot be reached.
Follow-up
- How do you recover which items were chosen, and what does that do to the space saving?
- Capacity is up to 10^9 but n is small: what do you change (for example, DP over value, or meet-in-the-middle)?
- Convert your memoized version to iteration: in what order must the table be filled?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Explain why the owner filter ignores the listing index
Medium · composite index · query plans · sort elimination
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
Approach
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
Follow-up
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
How would you design a payment processing service to ensure high availability?
Medium · System Design and Architecture
Approach
- Fix the scope first: payment creation, calls out to external processors or bank rails, a ledger, and status updates to clients. Decide which parts must answer synchronously and which can run asynchronously.
- Run stateless API servers behind a load balancer across availability zones. Require an idempotency key on every create-payment call and store it under a unique constraint, so a client retry returns the original result instead of charging twice.
- Model each payment as a state machine (created, submitted, succeeded, failed) in a replicated relational database with automatic failover. Record money movements in an append-only double-entry ledger. Write the state change and an outbox row in one transaction, and let a worker publish the outbox rows to a queue for idempotent consumers.
- Treat the processor as unreliable: set timeouts, retry with exponential backoff and jitter, and put a circuit breaker in front of it. If a call ends in an unknown outcome, mark it pending and settle it by querying the processor or by reconciling against settlement files.
- State the trade-off: never confirm a payment that is not durably recorded. Under failure, accept and queue requests rather than double-process them, and rely on reconciliation to catch drift.
Follow-up
- The processor timed out, but it may have charged the card. What does your system do next?
- How do you fail over the primary database without losing a payment that was already acknowledged?
- How would you detect and repair a mismatch between your ledger and the processor's settlement report?
How do you approach designing a system that must handle data synchronization across multiple platforms?
Medium · System Design and Architecture
Approach
- Clarify the shape of the sync: one-way or two-way, which system owns each field, how stale data may get, and the rate limits and auth model of each external platform.
- Capture changes with webhooks where a platform offers them, and back them up with periodic polling on an updated-since cursor so missed events are still caught. For your own database, use change data capture or an outbox to publish changes.
- Keep a mapping table from internal ids to external ids. Apply changes as idempotent upserts keyed on external id plus a version or timestamp, and process each account's changes through a partitioned queue so they apply in order.
- Choose a conflict policy and say what it costs. Last-writer-wins is simple but exposed to clock skew. Field-level ownership avoids most conflicts. Version checks can detect concurrent edits and flag them for a person to review.
- Handle failures: back off on 429 or 5xx responses, send poison messages to a dead-letter queue, run a reconciliation job that compares counts or checksums per account, and keep a full resync path for when incremental sync drifts.
Follow-up
- The same record is edited on both platforms within seconds. Which version wins, and how does the user find out?
- An external platform's API is down for a day. How does your system catch up without violating its rate limits?
- How would you backfill years of history for a new customer without starving incremental sync for everyone else?
What trade-offs do you consider when choosing between a monolithic and microservices architecture?
Medium · System Design and Architecture
Approach
- Monolith strengths: one deployable, in-process calls, ACID transactions that span modules, and simpler debugging, testing and operations. Costs: every release ships everything, the app scales only as a whole, and many teams contend for one codebase and one pipeline.
- Microservices strengths: each service deploys and scales on its own, failures stay isolated, and each team owns its service. Costs: network latency and partial failure, no cross-service ACID transactions (so you need sagas or an outbox), versioned APIs, distributed tracing, and more infrastructure to run.
- Base the decision on concrete factors: how many teams there are, how stable the domain boundaries are, whether some components need different scaling or isolation (a payment path versus batch reporting), and whether the team can operate the extra infrastructure.
- Name the middle path: a modular monolith with enforced module boundaries, then strangler-fig extraction of one service at a time, each with its own database. Splitting the shared database is usually the hardest step.
Follow-up
- Which module would you extract first from a monolith, and why that one?
- After the split, how do you handle an operation that used to be one database transaction across both modules?
- What are the signs of a distributed monolith, and how do you avoid building one?
How would you design a notification system that supports millions of users?
Medium · System Design and Architecture
Approach
- Pin down the requirements: channels (email, SMS, mobile push, in-app), triggers (event-driven or scheduled), per-user preferences and opt-outs, priority (a security alert versus a digest), and the delivery guarantee, normally at-least-once.
- Pipeline: producers publish events to a durable log or queue. A notification service resolves recipients, checks preferences and renders templates, then writes to per-channel queues. Channel workers call the providers (APNs, FCM, an email or SMS vendor).
- Retries mean duplicates are possible, so dedupe on an idempotency key per (event, user, channel). Retry with backoff, park failures in a dead-letter queue, and keep a fallback provider for each channel.
- Scale it: partition queues by user id, expand broadcast audiences in batches, rate-limit per user and per provider, and use separate high- and low-priority queues so a bulk campaign never delays transactional alerts.
- Store the preferences (with a cache in front) and a notification log for the in-app inbox and for audit. Process provider callbacks (delivered, bounced, invalid token) to update status and maintain suppression lists.
Follow-up
- How do you send one announcement to every user without delaying time-sensitive alerts?
- Can you guarantee a user never sees the same notification twice? Where does that guarantee stop?
- How do you handle a growing number of stale push tokens and hard email bounces?
Explain how you would ensure data consistency in a distributed system.
Medium · System Design and Architecture
Approach
- Decide the guarantee for each kind of data. Balances and ledgers need strong consistency; search indexes, caches and analytics can be eventually consistent. Under CAP, a network partition forces a choice between consistency and availability, and PACELC adds the latency-versus-consistency trade-off when there is no partition.
- Within one datastore, use single-leader replication with a synchronous replica for durable writes. Provide read-your-writes by reading from the leader or waiting for the replica to catch up. In leaderless stores, quorums with R + W > N make read and write sets overlap.
- Across services, two-phase commit can block if the coordinator fails, so prefer sagas with compensating actions. Use the transactional outbox to commit state and its event atomically, and make consumers idempotent with a processed-message table.
- Control concurrency inside the database: optimistic locking (UPDATE ... WHERE id = ? AND version = ?, then retry if no row changed), pessimistic SELECT ... FOR UPDATE, unique constraints, or SERIALIZABLE isolation for invariants that span rows.
- Verify continuously: run reconciliation jobs and invariant checks, such as debits equalling credits in a double-entry ledger, and alert on drift instead of assuming the protocol is perfect.
Follow-up
- Two concurrent withdrawals together overdraw an account. How do you prevent it?
- A saga's compensating step itself fails. What happens next?
- The queue redelivers a message after the consumer already applied it. How do you make that harmless?
One customer endpoint stalls deliveries to every other destination
Medium · bulkhead · timeouts · saturation
The egress service delivers about 1.5k webhooks/second across 40,000 destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. Throughput falls to 300/second, queue depth climbs, and p99 delivery latency for unaffected destinations goes from 200 ms to minutes, while the error rate barely moves. One tenant holds 900 destination rows whose URLs share a hostname that now answers in 9.5 seconds. Explain the mechanism with the arithmetic, then give the containment in the order you would apply it.
Approach
- Look at saturation before errors. A flat error rate with collapsing throughput says nothing is failing, things are waiting, so the first signal to pull is in-flight request count or pool wait time rather than the error counter. This is the distinction that decides the whole investigation.
- Group in-flight work by resolved host, not by destination id. The cap is keyed per destination row, so 900 rows sharing one hostname buy 3,600 concurrent slots against a single host, each held for 9.5 seconds. The bulkhead was never a bulkhead for that host, and grouping by the wrong dimension is why the dashboard looked healthy.
- Do the arithmetic in both directions. Required concurrency is arrival rate times latency, so 1.5k/second at 200 ms needs about 300 in flight, which is entirely consumed by 3,600 slow slots; conversely whatever concurrency is left sustains rate equals concurrency divided by 9.5 seconds, which is the 300/second you are seeing. Matching both numbers is what promotes this from a plausible story to the mechanism.
- Explain why the circuit breaker never helped. It opens on consecutive failures, and a 9.5-second response inside a 10-second timeout is a success. Slow is not failing, so an error-rate breaker cannot see this; you need a slow-call ratio, a deadline propagated from the caller's remaining budget, or a concurrency limiter.
Follow-up
- The host recovers to 80 ms. How long does the queue take to drain, and what does the drain do to the recovered host?
- Where should the 10-second timeout number actually come from?
Suggested practice checklist
The plan follows the three reported rounds: coding fundamentals and debugging for the Technical Evaluation, system design and behavioral stories for the Multi-Round Interview, with a resume pass first.
Day 1 — Map the loop and prepare your project stories
- Write the reported order: Initial Screening, Technical Evaluation, Multi-Round Interview. Beside each, note the format you still need to confirm with the recruiter.
- Choose three resume projects and write for each the problem, your decision, the trade-off and the result.
- Write a first version of your answer on why fintech and BILL, using the payments and data sync focus candidates describe.
Deliverable: A one-page loop map, three project summaries with a decision and result each, and a written answer to the fintech and BILL question.
Day 2 — Complexity and string or array transformations
- Solve three string or array transformation problems in your chosen language and write the complexity of each beside the code.
- For each, write the brute-force approach first, then an improved version using a hash map, two pointers or sorting, and say which constraint rules out the brute force.
Deliverable: Three solved problems with brute-force and improved versions, each annotated with time and space complexity.
Day 3 — Search and retrieval optimisation, plus edge cases
- Write out how you would speed up search over a dataset: hash lookup, sorted array with binary search, a tree, or an index, and what each costs to build and update.
- Write an edge-case checklist for your language (null, empty, duplicate, overflow, malformed format) and apply it to Day 2's solutions.
Deliverable: A comparison table of retrieval structures with costs, a written index explanation, and a revised set of Day 2 solutions with edge-case handling and tests.
Day 4 — Dynamic programming and recursion
- Implement a Knapsack-style problem both top-down with memoisation and bottom-up, and write the state definition and recurrence in a sentence.
- Write two recursive solutions on trees or linked lists, stating the recursion depth and what happens at the base case.
Deliverable: Working top-down and bottom-up Knapsack code, two recursive solutions, and a note on space for each.
Day 5 — Live debugging and defensive code
- Take one earlier solution and rewrite it with explicit error handling, then write tests for null and malformed inputs.
Deliverable: A recorded debugging narration with root cause, and one solution with error handling and passing edge-case tests.
Day 6 — System design for payments, sync and scale
- Write a one-page design for payment processing with high availability, covering idempotency, retries, a queue, failover and reconciliation.
- Write a one-page design for cross-platform data synchronisation, covering ordering, conflicts and replay, and a second for a notification system at scale.
- Write short answers for monolith versus microservices and for distributed consistency, naming the trade-off in each.
Deliverable: Three design outlines, two trade-off answers and one solved SQL exercise, each with failure cases listed.
Day 7 — Behavioral rehearsal and a full mock
- Write and rehearse out loud the five reported behavioral stories, each with a decision, a trade-off and a result.
- Do one mixed mock: a coding problem with complexity and edge cases, then a design outline, then two behavioral answers.
- Check your setup (editor, screen sharing, audio) and prepare questions for the recruiter.
Deliverable: Five rehearsed stories in situation, task, action, result form, a mock-session self-review and a completed setup check.
Behavioral preparation for BILL
Reported behavioral questions cover a technical disagreement, a pivot after changing requirements, feedback from senior engineers or product managers, a tight deadline with code quality intact, and your interest in fintech and BILL. Candidates also describe collaboration themes: working with product managers, designers, QA and DevOps, mentoring others and explaining complex ideas to non-technical colleagues. Prepare stories where you made the decision and can state the result.
How do you handle edge cases, such as null values or unexpected input formats?
Medium · Technical Fundamentals and Algorithms
Approach
- Answer in layers. At the boundary (API request, file import, third-party payload) check type, presence and range before any business logic. Separate a null value from a missing key, an empty string and a wrong type, because each needs a different response. Inside the logic, keep functions total over their declared input so the caller never meets a surprise exception.
- Make it concrete with a money example. Reject an amount that is negative, NaN or in an unexpected string format, hold currency amounts as integer minor units or a decimal type rather than a binary float, and return a clear 4xx with the offending field instead of a generic 500. For inbound sync data, accept unknown extra fields but fail on missing required ones.
- Describe what happens to bad records. Fail loudly with context in the log, route a rejected record to a quarantine or dead-letter queue so it can be inspected and replayed, and avoid broad catch blocks that return a default and hide the defect.
- Back it with a story: a real incident where an unhandled null or format reached production, how you found it from logs or a failing request, the fix, and the regression test you added. Then name your habit now, such as table-driven tests with null, empty, huge, duplicate and unicode inputs.
- For algorithm questions, say the checklist aloud before coding: empty input, one element, duplicates, negative numbers, integer overflow in Java, None in Python, null and undefined in TypeScript. The mistake that sinks this answer is a list of checks with no incident and no decision behind it.
Follow-up
- When do you reject bad input and when do you coerce it into a valid form?
- What do you do with a record that fails validation in a sync job that must keep running?
- How do you test for edge cases you have not thought of yet?
Describe a time you had to resolve a technical disagreement with a team member.
Medium · Behavioral and Leadership
Approach
- Choose a disagreement that was technical and had a real cost either way, such as caching versus recomputation, a schema choice, or synchronous versus queued processing. Avoid personality clashes and avoid a story where the other person was plainly wrong.
- State both positions fairly before yours, including the strongest argument for the other side. This shows you understood the trade-off, and it sets up how the disagreement was settled.
- Explain how you resolved it with evidence rather than seniority or persistence: a small benchmark, a prototype, a load test, a written comparison, or an agreement to ship the reversible option with a checkpoint to revisit. Name the decision rule you both accepted.
- Give the result and the cost: what shipped, what the data showed, and, if you were the one proved wrong, what you changed. Mention the working relationship afterwards. The mistake that sinks this story is one where you won by escalating, or conceded without ever stating your reasoning.
Follow-up
- What would you have done if the evidence had been inconclusive?
- How did you handle it when the other person was more senior than you?
- Did you ever change your mind during the discussion, and what changed it?
Tell me about a project where you had to pivot due to changing requirements.
Medium · Behavioral and Leadership
Approach
- Pick a project where the requirement moved after work had started, and say in one or two sentences what changed and who changed it, for example a product decision, a customer need or a compliance constraint. Keep the setup short and spend the time on your response.
- Show how you triaged: what you could keep, what you had to throw away, and how you estimated the new cost. Explain how you told the product manager the effect on scope and timeline before committing, rather than absorbing it silently.
- Describe the technical move that made the pivot cheap or safe, such as isolating the changing part behind an interface, using a feature flag, migrating in steps, or reusing the data model. Name the risk you were guarding against, for instance a half-migrated state in a payment flow.
- Close with the outcome in measurable terms (delivered, adopted, defects) and the lesson you applied afterwards, such as asking earlier about what is likely to change. The mistake that sinks it is blaming the stakeholder or telling a story where nothing was lost.
Follow-up
- How did you decide what to throw away and what to keep?
- How did you communicate the new timeline to people depending on you?
- What did you change in how you plan so the next pivot costs less?
How do you handle feedback from senior engineers or product managers?
Medium · Behavioral and Leadership
Approach
- Use specific feedback, not a general attitude. Good material is a code review from a senior engineer that changed how you design or test, or a product manager's pushback on scope or priority. Say what the feedback was in plain words.
- Show how you processed it: you asked clarifying questions until you understood the underlying concern, separated the substance from the delivery, and checked it against evidence such as the code, the metrics or the customer need.
- Show what you did, and that it lasted: you changed the code and also a habit, such as a test checklist, a smaller pull request size or an earlier design note. Add the result, for example fewer review rounds or a defect that no longer recurs.
- Include a case where you respectfully disagreed with feedback and answered with data or an alternative, to show you are neither defensive nor passive. The mistake that sinks the answer is a claim that you welcome all feedback with no example, or a disguised strength presented as a weakness.
Follow-up
- Tell me about feedback you disagreed with and what you did.
- How do you give feedback to a more senior engineer?
- What was the most useful piece of feedback in your last year and what changed because of it?
Describe a situation where you had to manage a tight deadline while maintaining code quality.
Medium · Behavioral and Leadership
Approach
- Set up the constraint: what was due, why the date was fixed, and what quality meant on that change, such as no regressions on a payment path or a clean upgrade path. Make clear that you made the call on how to protect it.
- Explain how you cut scope rather than quality. Say what you postponed, which parts you protected with tests because failure would be costly, and what you accepted as rough, for example an internal screen or a non-critical edge case.
- Name the safety mechanisms you added to ship fast: a feature flag, a staged rollout, extra monitoring and alerts on the new path, and a rehearsed rollback. This turns a vague promise about quality into concrete practice.
- Show communication: you told the product manager and your team what was being traded, and you logged the deferred work as tracked tickets, then actually completed it. Give the result, such as delivery on time with no incidents.
- The mistake that sinks this story is a hero narrative of long hours, or a claim that nothing was traded away. A credible answer names the trade and shows it was deliberate.
Follow-up
- What did you leave out, and how did you decide that was acceptable?
- How did you make sure the deferred work was done?
- What would you do if the deadline had been impossible even after cutting scope?
Why are you interested in the fintech space, and specifically, BILL?
Medium · Behavioral and Leadership
Approach
- Anchor the answer in what candidates describe about BILL: software that handles payments and synchronises financial data for small and midsize businesses, with work spread across a Payments Platform, Infrastructure and Product features. Read the product pages and use what you find there, so the answer comes from your own research and not from slogans.
- Connect it to your own experience with a concrete example: payments, billing, integrations, reliability work, or APIs where correctness mattered. Name the engineering problems that attract you, such as handling retries and duplicates safely, keeping records consistent between systems, or making a financial workflow harder to get wrong.
- Say what you want from the role in terms you can back: owning a feature from design to deployment, working with product managers and QA, and building on a stack such as the one candidates list (Java, Python or TypeScript with REST APIs).
- Keep it honest and specific, and tie it to a story or decision from your history. The mistake that sinks the answer is generic praise for fintech growth or for the company, with nothing linking it to your work.
Follow-up
- Which of your past projects is closest to the work you would do here?
- What part of working on a payments system would you find hardest?
- What do you want to have learned after your first months in the role?