Interview guides · BharatPe
BharatPe Software Engineer Interview Guide
BharatPe Software Engineer interview guide: reported DSA, SQL and system design topics, behavioral prompts and a 7-day preparation plan.
The 60-second brief
Candidates describe BharatPe as a fintech company serving Indian merchants. The product areas they mention are payment gateways and lending and credit solutions, used by millions of people. A Software Engineer builds and scales those products, and the reported working conditions are high concurrency, large data throughput and transactions that must stay correct. Candidates also mention microservices for new financial products and tuning database queries that handle high transaction volume.
This guide is for candidates interviewing for a Software Engineer position at BharatPe, most usefully for backend-leaning work. It covers the five data-structure and string questions candidates report, the SQL, system design and complexity topics they list, the behavioral themes they describe, and a seven-day plan. No pay information is included, and no fixed round order has been reported, so confirm the stages with your recruiter.
Candidates describe a process that often opens with an online assessment or recruiter screen, continues with one or more technical rounds on coding and system design, and ends with a discussion of technical fit, culture and business impact. The sequence can vary by team or subsidiary.
Focus areas
- Data Structures & Algorithms (DSA)
- SQL
- Algorithmic Problem Solving
Overview
The reported questions are classic fundamentals: Next Greater Element, Valid Parentheses with a stack, linked-list and tree traversal, hash map lookups and their internals, and string-to-map conversion. None needs an exotic algorithm. Each is easy to solve badly and easy to solve well, and the difference is in what you state aloud: the complexity, the empty and malformed inputs, and why you picked the data structure.
Candidates also list SQL as a frequent gatekeeper for backend roles, alongside system design that covers scalability under traffic spikes, SQL versus NoSQL choices, and the latency versus consistency trade-off. Because the product handles payments and lending, it pays to practise talking about correctness under concurrency: what happens when two writes hit the same row, and how an index changes a slow query.
The stack candidates report is an object-oriented language (Java, Go or Python), SQL, and nice-to-have familiarity with microservices and AWS. Pick the one language you are fastest in and write every practice solution in it, including tests for empty input, a single element and large inputs.
Finally, candidates describe discussions with senior engineering leaders and questions about motivation for switching, working at pace and handling professional challenges. Plan to walk through every project on your resume with the reasons behind each architectural choice, because that conversation is reported alongside the technical rounds.
Where to focus your preparation
Candidates report no strict round order, and the focus can shift by team or subsidiary, so treat the stage below as a map of what to prepare and confirm the real sequence with your recruiter.
Preparation focus
Candidates describe a screening stage first, either an online assessment or a recruiter conversation, then one or more technical rounds on coding and system design, then a final discussion that weighs technical fit against culture and business impact. Some report meeting senior engineering leaders such as tech leads, VPs of engineering or the CTO. Since the order is not fixed, ask your recruiter which stages apply to your team, whether you will write code live, and whether SQL is included, then prepare all three areas: data structures, SQL and system design.
What to demonstrate
- Working, readable code for stack, list, tree, hash map and string problems, with the complexity stated
- Handling of empty, null and very large inputs
- SQL fluency for a backend role, including queries over high-volume transactional data
- Trade-off reasoning in design, such as latency versus consistency and SQL versus NoSQL
How to prepare
- Write the Next Greater Element and Valid Parentheses solutions in your chosen language from memory and say the complexity aloud for each
- Be able to explain how a hash map stores, finds and resizes entries, since the hash map question asks for internals as well as lookups
- Prepare a short walkthrough of each resume project covering the architecture choice, one challenge and what you would change
Where candidates lose points
Stopping at the brute-force nested loop for Next Greater Element, or returning the wrong answer when values repeat
Name the O(n^2) scan, then move to a monotonic stack of indices that pops while the current value is strictly greater, giving O(n). Decide aloud what to return when no greater element exists and how equal values behave, and offer the circular variant if asked.
A Valid Parentheses solution that counts brackets, or crashes by popping an empty stack
Push expected closing brackets or the openers, check the top before each pop, and return false when the stack is empty on a closer. Finish by checking the stack is empty, and test inputs like a lone closer, an odd length and interleaved types such as ([)].
Saying a hash map is O(1) without explaining collisions, resizing or the equals and hashCode contract
Explain hashing to a bucket, collision handling by chaining or probing, load factor and rehash on growth, and why worst case is O(n). State that mutable keys or a missing hashCode override break lookups, and that average and worst-case cost differ.
Starting to code the string-to-map conversion before clarifying the format
Ask about delimiters, duplicate keys, missing values, whitespace, and values that contain the separator before writing anything, then put those answers in your test cases. Reported guidance also stresses asking clarifying questions on ambiguous problem statements.
Treating SQL and design as afterthoughts, then giving a one-sided design with no trade-off
Reserve prep days for joins, aggregation and index use on transaction-style tables. In design answers, state the trade-off you chose, for example accepting higher latency for stronger consistency on balances, and why SQL or NoSQL fits that data.
Questions & practice
Implement a function to find the Next Greater Element in an array.
Medium · Data Structures and Algorithms
Approach
- Start from the brute force: for each index scan right until a larger value appears. That is O(n^2) in the worst case, for example on a strictly decreasing array where every scan runs to the end.
- Use a monotonic stack of indices whose values decrease from bottom to top. Walk left to right; while the stack is non-empty and arr[top] < arr[i], pop top and set result[top] = arr[i]; then push i. Indices still on the stack at the end get -1.
- Each index is pushed once and popped at most once, so the total work is O(n) amortized, with O(n) extra space for the stack and the result array.
- Edge cases: an empty array returns an empty result; equal values are not 'greater', so pop only on a strict <; a strictly increasing array resolves every element immediately, and a strictly decreasing one leaves everything as -1.
- Store indices on the stack, not values. That lets you write into the result array directly, and it handles the variants that ask for the distance to the next greater element.
Follow-up
- How do you handle a circular array where the search wraps around? (Iterate i from 0 to 2n-1 using arr[i % n], and push indices only during the first pass.)
- Given a subset array whose elements all appear in the main array, how do you answer next-greater for each of them? (Run the stack once over the main array and put the answers in a hash map keyed by value.)
- What changes for the next smaller element, or for the next greater element to the left?
Solve a problem involving Valid Parentheses using stacks.
Medium · Data Structures and Algorithms
Approach
- Push every opening bracket onto a stack. On a closing bracket, check that the stack is non-empty and that its top is the matching opener (look it up in a map from closer to opener), then pop. The string is valid only if the stack is empty at the end.
- This is O(n) time and O(n) space in the worst case (all openers). An odd-length string can be rejected immediately.
- Edge cases: an empty string is valid; a string that starts with a closer fails on the empty-stack check; leftover openers such as '((' fail on the final empty check; interleaved input such as '([)]' fails on the top-of-stack comparison.
- With only one bracket type, an integer counter that must never go negative and must end at zero gives O(1) space. Explain why a counter per type is not enough with several types: it accepts '([)]'.
Follow-up
- How would you find the length of the longest valid parentheses substring? (Use a stack of indices seeded with -1, or two counter passes, both O(n).)
- What is the minimum number of insertions or removals needed to make the string valid?
- How do you generate every valid string of n bracket pairs, and how many are there? (Use backtracking with open/close counts; the count is the nth Catalan number.)
Explain the logic behind traversing a linked list or managing tree structures.
Medium · Data Structures and Algorithms
Approach
- For linked lists, traverse with a single pointer until null, in O(n). Reverse in place with prev/curr/next pointers in O(n) time and O(1) space. Use a dummy head so that deleting or inserting at the head needs no special case.
- Use fast and slow pointers for the middle node and for cycle detection (Floyd). After the two pointers meet, reset one to the head and step both one node at a time; they meet again at the start of the cycle.
- For trees, explain DFS in its three orders (preorder, inorder, postorder), written both recursively and iteratively with an explicit stack, and BFS level-order with a queue. Each visits every node once in O(n). DFS uses O(h) stack space and BFS uses O(w), where w is the widest level.
- An inorder traversal of a BST yields the keys in sorted order, and search or insert costs O(h). That is O(log n) when the tree is balanced and O(n) when it is skewed. A skewed tree also makes recursive DFS deep enough to overflow the call stack.
- Edge cases: an empty list or tree, a single node, two nodes (for reversal and the middle), and a cycle that includes the head.
Follow-up
- How do you validate a BST? (Pass down min/max bounds; comparing each node only with its direct children is wrong.)
- How would you reverse a linked list in groups of k, or find the lowest common ancestor of two nodes in a binary tree?
- Can you do an inorder traversal with O(1) extra space? (Use Morris traversal, which threads temporary right pointers and restores them.)
Perform efficient lookups using HashMaps and explain their internal mechanics.
Medium · Data Structures and Algorithms
Approach
- Use the map where it pays off: one-pass two-sum (check target - x before inserting x), frequency counting, and grouping, such as anagrams keyed by their sorted string. Each replaces an O(n^2) pair scan with O(n) average time and O(n) space.
- Internals (Java HashMap): a bucket array whose capacity is a power of two. The index is (n - 1) & hash, after the hash is spread with h ^ (h >>> 16). Colliding entries chain in a bucket, and since Java 8 a bucket becomes a red-black tree at 8 entries once the table has at least 64 slots; below that the table resizes instead.
- The default capacity is 16 and the load factor 0.75. Passing the threshold doubles the table and redistributes the entries, so put stays O(1) amortized. get and put are O(1) on average and degrade to O(n) for a list bucket, or O(log n) for a treeified bucket, under heavy collisions.
- The key contract: equal objects must return equal hashCode values, and keys must not change while they are stored. A mutated key lands in the wrong bucket and its entry can no longer be found.
- Java HashMap allows one null key and is not thread-safe. Use ConcurrentHashMap, which rejects null keys and values, for shared access. Python dict, by contrast, uses open addressing and has preserved insertion order since 3.7.
Follow-up
- What happens to performance if every key's hashCode returns the same constant?
- When would you choose TreeMap or LinkedHashMap instead, and how do you build an LRU cache from LinkedHashMap? (Use access order plus removeEldestEntry.)
- Why does Java size the table as a power of two, and how does ConcurrentHashMap differ from Collections.synchronizedMap?
Write code to handle string-to-map casting or common string manipulation tasks.
Medium · Data Structures and Algorithms
Approach
- Before coding, pin down the format: the pair and key-value delimiters (for example 'a=1;b=2' or a query string), whether whitespace is trimmed, what to do with duplicate keys (last wins or collect a list), empty values, segments with no '=', and whether values can be quoted or URL-encoded.
- Split on the pair delimiter, then split each segment on the first '=' only (Java split("=", 2), Python str.partition('=')) so that values containing '=' survive. Skip empty segments and trim keys and values. This is O(n) in the input length.
- Know the API traps: Java String.split takes a regex, so a delimiter such as '|' or '.' must be escaped, and with the default limit it drops trailing empty strings. Build output with StringBuilder or ''.join, because repeated += on immutable strings can degrade to quadratic time.
- When values must be cast to types, convert inside a guarded block (NumberFormatException, ValueError) and decide whether a bad value rejects the whole input or only that key. For real JSON, parse with a library (json.loads, Jackson's ObjectMapper.readValue into a Map) rather than splitting by hand.
- For the related string tasks, have the standard patterns ready: an anagram check with a 26-slot count array in O(n), the first non-repeating character with a count map and a second pass, and reversing words by splitting, reversing and joining.
Follow-up
- How do you support quoted values that contain the delimiter itself? (Write a character-by-character state machine that tracks whether it is inside quotes and handles escapes.)
- How would you serialize the map back so that a round trip is deterministic? (Sort the keys and escape the delimiters.)
- How does the parser change if the input is a large stream that does not fit in memory?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
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?
Convert the listing endpoint from offset pages to stable cursors
Medium · pagination · cursors · index access path
GET /v1/resources returns a tenant's resources newest-updated first, today with page and per_page, backed by index (tenant_id, status, updated_at DESC, resource_id DESC). Callers are a browser feed and a nightly sync job that walks every page. Users report items appearing twice or vanishing between pages, and page 400 is slow. Design the cursor contract: what the cursor contains and how it is encoded, the exact WHERE and ORDER BY, what happens when a row's updated_at changes mid-walk, how a client detects the end, and what the sync job does when a cursor is rejected.
Approach
- Separate the two defects, because they have different fixes. Cost: OFFSET n makes the engine produce and discard n rows, so price grows with page depth rather than page size and page 400 pays for 400 pages of work. Correctness: while the set shifts, rows cross the offset boundary and are skipped or repeated, and nothing in the response lets the client detect it.
- Write the seek: WHERE tenant_id = $1 AND status = $2 AND (updated_at, resource_id) < ($k, $id) ORDER BY updated_at DESC, resource_id DESC LIMIT n. The tie-break is not decoration - updated_at is not unique, and two rows sharing a timestamp across a page boundary reintroduce exactly the skip this was adopted to remove. PostgreSQL seeks the composite index on the row-value comparison directly; on an engine that does not optimise a row constructor, expand it into the equivalent OR form or the plan quietly degrades to a scan.
- Encode the cursor as an opaque token carrying the sort key, the id, and a fingerprint of the filter and sort order, signed or at minimum validated. A cursor replayed against a different sort or filter must be a 400 with its own code, not a silently wrong page - the sync job cannot notice the difference otherwise.
- State the guarantee precisely rather than generously. Keyset is stable against inserts and deletes elsewhere in the set, because the position is a value and not a count. It is not a snapshot: updated_at is mutable, so a row that is edited during the walk re-sorts and may be seen twice or not at all. If the sync job needs exactly-once coverage, order on an immutable key such as (created_at, resource_id), or walk resource_revision by revision_id and treat updated_at as data.
Follow-up
- The client wants a total count and the ability to jump to page 400. What can you honestly offer instead, and what does each option cost?
- How do you paginate backwards, and what does that require of the index?
Keep one unresponsive destination from stalling all webhook delivery
Medium · fan-out · circuit breaker · timeouts
Egress delivery sends about 1.5k webhooks/second to 40k destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. One destination begins accepting connections and never responding; within the hour 150 destinations behave the same way. Design the delivery path so unrelated destinations are unaffected: the pool structure, the timeouts, the retry policy, the per-destination circuit, and what is recorded so a retry is not a second effect at the receiver. State how many in-flight slots the degraded destinations hold and why that number decides the design.
Approach
- Start with the number, and with the law that produces it. In-flight work is arrival rate times time in service, so 1.5k/second against a healthy 200 ms response needs about 300 concurrent slots. Per destination the same product applies, ceilinged by the concurrency cap: at the fleet average of 0.0375 deliveries/second per destination (1.5k spread over 40k) a 10-second timeout is 0.375 slots. A destination that has queued retries behind it is a different regime - every slot refills the instant an attempt expires, so it sits pinned at its cap of 4 - and 150 of those hold 600 slots, more than a pool sized for healthy traffic, entirely consumed by endpoints that will never answer. The per-destination cap bounds one endpoint and says nothing about the aggregate, which is exactly why it alone is not containment.
- Contain with bulkheads and an admission bound rather than a larger pool. Cap total in-flight per pool and shard destinations across pools by a hash of destination id, so a correlated group - one provider, one region - cannot exceed its pool's share. A delivery refused admission and re-queued with backoff is strictly better than one holding a slot on behalf of a receiver that is not listening.
- Treat the timeout as two timeouts, and be exact about what shortening one buys. Connect and read are separate failures and both must be shorter than the budget of whatever is waiting. Occupancy is min(cap, arrival rate x timeout), so dropping the read ceiling from 10 seconds to 3 cuts a merely slow destination's occupancy proportionally, 0.375 slots to 0.11 at the fleet average. It does not cut the 4 slots held by one of the 150: a destination with a retry backlog arrives far above cap/timeout - 0.4/second at a 10-second timeout, 1.33/second at 3 - so it stays pinned at the cap either way and only the slot-seconds per attempt fall. What that does buy is detection rate: 3.3x more failures observed per second on the same four slots, which is how fast the circuit reaches its threshold. Pick the value from the measured latency distribution of successful deliveries, with their high percentile as the floor, not from a round number.
- Add a circuit per destination, counting a timeout as a failure. Once open, fail fast without taking a slot - that is the whole point, converting 4 held slots into zero. Half-open on a schedule with exactly one probe and close only if the probe succeeds, so a permanently dead endpoint costs one request per interval instead of a growing retry queue.
Follow-up
- The destination is not dead - it answers in 9.5 seconds with a 200. Does a failure-rate circuit open? Should anything shed that traffic, and on what signal?
- One destination requires deliveries in order. What does a per-destination concurrency of 4 do to that guarantee, and what would you change to offer it?
One log partition stops advancing while the others drain
Easy · poison message · head-of-line blocking · consumer lag
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
Suggested practice checklist
The plan starts by confirming the format, spends four days on the reported coding topics, then covers SQL, system design and a final mock plus behavioral rehearsal.
Day 1 — Confirm the loop and pick your language
- Ask your recruiter whether there is an online assessment, which technical rounds apply, whether you will write code live, and whether SQL and system design are included
- Choose one of Java, Go or Python and set up a template with a test harness so every solution comes with its edge-case tests
Deliverable: A short written list of the confirmed stages and the language you will use, plus a ready template file.
Day 2 — Stack problems: Next Greater Element and Valid Parentheses
- Write Next Greater Element first as a nested loop, then as a monotonic stack of indices, and state both complexities
- Add the circular-array variant and the behavior for equal values
- Write Valid Parentheses for three bracket types and test a lone closer, an odd length and interleaved types
Deliverable: Two working functions with the written complexity of each and a list of the edge cases tested.
Day 3 — Linked lists and trees
- Write iterative list reversal, find-the-middle with fast and slow pointers, and cycle detection
- Write inorder traversal recursively and with an explicit stack, plus level-order with a queue
- Explain aloud what changes for a skewed tree and for a null root
Deliverable: Five traversal or pointer functions plus a spoken two-minute explanation of when to use recursion, a stack or a queue.
Day 4 — Hash maps and string handling
- Build a small hash map with chaining and resizing, then explain how it differs from your language's built-in map
- Write a parser that turns a delimited key-value string into a map, covering duplicate keys, missing values, extra whitespace and values containing the separator
- Write the explanation of equals and hashCode, or the equivalent in Go or Python, and what breaks with mutable keys
Deliverable: A working mini hash map, a tested string-to-map parser, and a one-paragraph note on collision handling and resizing.
Day 5 — SQL on transactional data
- Work the practice question on why an owner filter ignores a listing index, and rewrite the query or index so it is used
- Write joins and grouped aggregates over a payments-style table you design, with an index for each filter
Deliverable: Two written answers from the practice set and a short schema with three queries and the index each one needs.
Day 6 — System design and trade-offs
- Work the practice question on converting offset pages to stable cursors and name the index it needs
- Work the practice question on one slow webhook destination stalling delivery, covering queues, retries and isolation
- Write a one-page answer on handling a sudden traffic spike, with a SQL versus NoSQL choice and a latency versus consistency decision
Deliverable: Two design write-ups from the practice set and a one-page spike-handling design that names each trade-off.
Day 7 — Mock loop and behavioral rehearsal
- Solve two of the reported coding questions aloud against a timer you set yourself, starting by asking clarifying questions
- Rehearse your motivation for switching and a walkthrough of each resume project
- Practise the code review, deadline and ownership prompts from the practice set
Deliverable: A recorded or written mock run, a one-minute motivation answer, and three project walkthroughs with the reason behind each design choice.
Motivation, pace and project stories
Candidates report that the later conversations cover motivation for switching, working in a fast-paced setting, handling professional challenges, and the reasoning behind every project on your resume. Prepare concrete stories in which you made the decision, state the outcome, and name what you would do differently.
- Why are you looking to switch, and why this role now?
- Tell me about a time you worked under a tight deadline: what you shipped, what you deferred and how you limited the risk.
- Walk me through the most significant project on your resume: why you chose that architecture and what went wrong.
- Describe a professional challenge and how you handled it.
- Tell me about a time a business requirement was ambiguous and how you turned it into a technical plan.
Turn a code review disagreement into a decision
Easy · code review · optimistic concurrency · communication
A colleague's change updates a row with UPDATE resource SET version = version + 1 WHERE resource_id = $1 AND version = $2 and treats an affected-row count of zero as a successful no-op. You read that as a silently lost update; they think returning 200 is friendlier to clients than returning a conflict. Describe how you have handled a review disagreement of this shape: what goes in the comment, when you leave the thread, and who decides. Then write the comment you would leave here, in under 80 words.
Approach
- Sort the disagreement before writing anything. A silently discarded write is a correctness claim about data; the choice between 409 and 412 is taste. Only the first justifies blocking a merge, and saying which one you are doing is most of the value of the comment.
- Make the claim reproducible in the comment itself with an interleaving rather than a principle: A reads version 7, B reads version 7, B commits version 8, A's predicate matches zero rows, A is told it succeeded and A's edit is gone.
- Offer the alternative with its cost attached: return 409 carrying the current version and the revision that won, so the client can re-read and re-apply. Note that automatic retry is not the fix, because a retry re-reads the winner's state and reapplies an intent formed against data that no longer exists.
- Apply an escalation rule you can state: two round trips on the thread, then a call, and the service's owner decides rather than the reviewer. A reviewer who cannot be overruled is a bottleneck with extra steps.
Follow-up
- Where would you put the test that fails if someone reintroduces the swallowed zero rowcount?
- The author says clients cannot handle a 409. How do you check whether that is true?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?