Interview guides · Bubble
Bubble Software Engineer Interview Guide
Bubble Software Engineer interview guide: coding, data structures, dynamic programming and system design questions, with a 7-day preparation plan.
The 60-second brief
Bubble builds a platform on which people create web applications through a visual programming environment instead of writing traditional code. Candidates describe the Software Engineer role as work on that platform itself: core features, the editor, backend infrastructure and integrations. The challenges they mention are state management inside a visual environment, editor responsiveness, and the performance of applications that users assemble rather than engineers write. The requirements candidates list are JavaScript or TypeScript with React, scalable backend systems and solid computer science fundamentals.
This guide is for engineers interviewing for a Software Engineer position at Bubble. It covers the two families of questions candidates report, coding and algorithms (data structures, complexity, edge cases, dynamic programming) and system design (a high-traffic backend, concurrency), plus the conversation about your own past work and your reasons for working on a no-code product. Several reported questions are patterns with a blank to fill, such as the bracketed algorithm or constraint, so the guide teaches you to prepare a family of answers rather than one script.
Candidates describe a progression from a recruiter screen through deeper technical and behavioral stages, ending for some roles with founders or senior leadership. The structure varies by team and seniority, and reports mention roughly 3 to 4 weeks from application to offer.
Focus areas
- System Design
- Coding Interview Problem Solving
- Scalability Analysis
Overview
The product is the tool users build their own software in, so the engineering problems candidates describe differ from a typical CRUD service: state that changes as someone edits, an editor that has to stay responsive, and applications whose shape is decided by users rather than by engineers. That does not mean the interview is a product quiz. The reported question types are classic ones: implement a data structure, state its time and space complexity, handle edge cases, write an algorithm and explain it, compare top-down and bottom-up dynamic programming, and design a backend for high traffic or high concurrency.
Candidates also list evaluation areas that are not questions in themselves: arrays, linked lists, trees and hash maps; Big O reasoning; readable, modular code; a graph traversal problem; refactoring a snippet to run faster; scalability analysis; and explaining the system design of a project you built before. Plan for all of them. The refactoring prompt in particular rewards a habit of finding the repeated work (a nested loop, a repeated lookup, a recomputed subproblem) and naming the structure that removes it.
Because the stack candidates describe is JavaScript or TypeScript with React, do your coding practice in one of those languages so that syntax never costs you time. Know how Map and Set behave, what array methods cost, and why recursion depth matters in JavaScript when you write a top-down solution.
Finally, candidates note that applicants who have used the Bubble platform arrive with a clearer view of what it can and cannot do. Spend an evening building something small in it. It gives you concrete material for the conversation about why you want the role, and for design questions where you can reason about what a visual editor has to store and recompute.
Where to focus your preparation
No single sequence is confirmed in the reports, so this guide treats the loop as one preparation block covering the coding, design and conversation stages candidates describe. Confirm the actual format with your recruiter.
Preparation focus
Candidates describe a general progression: a recruiter screen, deeper technical and behavioral stages, and for some roles a final conversation with founders or senior leadership. The exact structure varies by team and seniority, so ask your recruiter which stages you will face and whether each involves writing code. The technical content they report falls into two groups. Coding and algorithms covers arrays, linked lists, trees and hash maps, complexity analysis, a graph traversal problem and refactoring a snippet for speed. System design covers backend scalability, high traffic and concurrency, and explaining the design of something you built before. Prepare each group separately: for coding, practise writing working code in TypeScript or JavaScript while narrating assumptions; for design, practise a repeatable outline from requirements to bottlenecks.
What to demonstrate
- Working code for data structure and algorithm problems, with correct complexity analysis
- Handling of edge cases and readable, modular structure in the code you write
- Backend design reasoning about scale and concurrency
- Clarity when explaining your own earlier system design work
How to prepare
- Implement a hash map with resizing, a linked list, a min-heap and an LRU cache from scratch in TypeScript, then state the complexity of each operation aloud
- Write breadth-first and depth-first traversals on an adjacency list, and refactor one deliberately slow snippet, naming the repeated work you removed
- Solve one dynamic programming problem both top-down with memoization and bottom-up with a table, and write when you would choose each
- Prepare a design outline (requirements, read and write paths, storage, bottleneck, failure) and one past system you can diagram and defend
Where candidates lose points
Starting to code a data structure before agreeing on its operations
Ask which operations are needed and what they must cost before you write anything. A hash map that never resizes or an LRU cache that scans a list is a correct-looking answer with the wrong complexity; say the target cost of each operation first, then build to it.
Giving complexity as a final line instead of reasoning through it
Count the work as you write: how many times each loop runs, what each Map or Set call costs, how deep the recursion goes and what the memo table holds. Then state time and space separately, including recursion stack space, and say which input shape produces the worst case.
Describing top-down and bottom-up dynamic programming as the same thing with different syntax
Know the actual differences: memoized recursion solves only the subproblems it reaches but pays call overhead and can overflow the stack on deep inputs, while a table fills every state in a fixed order and allows space reduction to the last row. Practise one problem both ways so you can show the difference rather than recite it.
A design answer that lists components (cache, queue, load balancer) but never names a bottleneck
For the high-traffic and high-concurrency questions, separate the read path from the write path, say which one saturates first and what you do about it, then say what state needs consistency and what can be stale. End by describing what happens when a dependency is down.
Being unable to explain your own past system, or going silent when the pace feels fast
Pick one system you built and prepare its requirements, the alternatives you rejected and what you would change today. Some candidates report feeling rushed; asking for a moment to restate the problem or clarify the requirement before you start is a reasonable move, and rehearsing it makes it easy to do.
Questions & practice
How would you implement a specific data structure from scratch?
Medium · Technical & Algorithmic Proficiency
Approach
- Pin down the operations and their target costs before choosing a layout. For an LRU cache with O(1) get and put, that means a hash map from key to node plus a doubly linked list ordered by recency. Sentinel head and tail nodes remove the null checks on insert and unlink.
- For a binary min-heap stored in an array, the children of i are 2i+1 and 2i+2 and its parent is (i-1)>>1. Push appends and sifts up; pop moves the last element to the root and sifts down. Both are O(log n), peek is O(1), and heapifying an existing array bottom-up is O(n).
- For a hash map, use a bucket array indexed by hash mod capacity, with chaining or open addressing. Double the capacity and rehash every key once the load factor passes a threshold such as 0.75. Inserts are amortised O(1) on average and O(n) in the worst case. Deletes under open addressing need tombstones.
- Before writing the code, state the edge cases and how each behaves. Pop or peek on an empty structure should either throw or return undefined, and you say which. An LRU cache can have capacity 0 or 1. Updating an existing key moves it to most-recent without increasing the size.
- Check the class with a short sequence of operations whose results you know, then give time per operation and total space (O(n) for each of these).
Follow-up
- How would you add decrease-key to the heap without an O(n) search for the element?
- Why does doubling the capacity give amortised O(1) appends, while growing by a fixed amount does not?
- What changes if two threads call get and put at the same time?
Can you explain the time and space complexity of your solution?
Medium · Technical & Algorithmic Proficiency
Approach
- Name the variable behind each term and count the dominant operation. Nested loops over two inputs cost O(n·m), not O(n²). A loop that halves its range each pass costs O(log n). Sorting first adds O(n log n).
- For recursive code, write the recurrence and solve it. T(n) = 2T(n/2) + O(n) gives O(n log n), as in merge sort. Memoised recursion costs the number of distinct states times the work done per state.
- Point out costs hidden inside library calls. In JavaScript, Array.prototype.shift and splice near the front are usually O(n), slice and spread copy the whole array, array.includes scans linearly where Set.has is O(1) on average, and repeated string concatenation in a loop can rebuild the string each time.
- Report auxiliary space separately from the input and output. Count the recursion stack: tree DFS uses O(h), which becomes O(n) on a skewed tree. A hash map holds O(number of distinct keys).
- Say which case you mean: worst, average or amortised. Hash lookups are O(1) on average and O(n) in the worst case, dynamic array append is amortised O(1), and quicksort averages O(n log n) with an O(n²) worst case.
Follow-up
- Can you bring the extra space down to O(1), and what does that cost you in time or readability?
- How does the complexity change when the input is already sorted or contains only duplicates?
- Where would the constant factor matter more than the Big O for realistic input sizes?
How do you approach handling edge cases in a standard coding problem?
Medium · Technical & Algorithmic Proficiency
Approach
- List edge cases by input class before writing code: empty input, one element, two elements, all elements equal, input that is already sorted or reverse sorted, zeros and negative numbers, and the maximum stated size.
- Check boundaries and numeric limits. Off-by-one errors usually come from loop bounds and binary search: be clear whether the loop is lo <= hi or lo < hi, and compute mid as lo + ((hi - lo) >> 1). In JavaScript, integers above Number.MAX_SAFE_INTEGER (2^53 - 1) lose precision, so use BigInt when values can get that large.
- Check the shape of the structure as well. Inputs can be null or undefined. Linked lists and graphs can contain cycles, so keep a visited set. Graphs can have disconnected components and self-loops. String.length in JavaScript counts UTF-16 code units, so characters such as emoji count as 2.
- Decide what each case returns (an empty result, -1 or an exception), say it out loud as an assumption, and handle it in guard clauses at the top of the function so the special cases are not spread through the main loop.
- Trace the smallest cases by hand against the finished code, keep them as assertions, then add one large or random input to check performance.
Follow-up
- How would you test these cases automatically, for example with property-based tests?
- What should the function do if invalid entries appear partway through a stream of input?
- Which edge case did your first version miss, and how did you find it?
Write a function to solve [specific algorithm] and explain your logic.
Medium · Technical & Algorithmic Proficiency
Approach
- Restate the function signature with a small example and confirm the constraints. Then match the problem to an algorithm. For the graph traversal problem candidates report, unweighted shortest path means BFS with a queue, and connectivity or cycle detection means DFS or union-find. With non-negative edge weights, use Dijkstra with a min-heap in O((V + E) log V).
- Build an adjacency list from the edge list in O(V + E). In BFS, mark a node visited when you enqueue it, not when you dequeue it, so no node enters the queue twice. Time and space are both O(V + E).
- In JavaScript, use a head index for the queue rather than calling queue.shift() in the loop, which keeps BFS linear. Recursive DFS on a deep graph can throw RangeError: Maximum call stack size exceeded, so be ready to switch to an explicit stack.
- Explain why the algorithm is correct through its invariant. BFS dequeues nodes in non-decreasing order of distance from the source, so the first time it reaches the target is a shortest path. Walk through a small example to show it.
- Cover the edge cases: start equal to target, an unreachable target (return -1 or null and say which), disconnected components, cycles, and whether edges are directed or undirected.
Follow-up
- How would you return the path itself instead of its length? (Keep a parent map and walk it back from the target.)
- What changes if edges have weights, and what if some weights are negative?
- How would you handle a graph that is too large to build in memory, such as an implicit grid?
What are the trade-offs between top-down and bottom-up dynamic programming?
Medium · Technical & Algorithmic Proficiency
Approach
- Both approaches use the same recurrence and differ in evaluation order. Top-down is recursion plus a memo (an array or a map keyed by state) and computes only the states it reaches. Bottom-up fills a table in an order where every dependency is ready before it is needed.
- Asymptotic time is the same either way: number of states times transitions per state. Coin change, for example, costs O(amount × number of coins) in both forms.
- Top-down follows the recurrence directly and does better when only a small part of the state space is reachable. The costs are function-call overhead, map lookups when the state is a composite key, and recursion depth. Deep recursion can overflow the call stack in JavaScript.
- Bottom-up needs no recursion and reads memory more predictably. It also allows space reduction: Fibonacci in O(1), longest common subsequence with two rolling rows in O(min(m, n)), and 0/1 knapsack in a 1D array if you iterate capacity downward. The cost is working out the fill order and computing every state, including ones never needed.
- Recovering the actual solution, not just its value, needs either the full table or parent pointers. Rolling-row space reductions throw that information away, so say so when you choose one.
Follow-up
- Convert your memoised solution to bottom-up and say what order you fill the table in.
- Why does the 1D 0/1 knapsack iterate capacity from high to low, and what goes wrong if you go upward?
- When would you keep the top-down version even though it uses more memory?
Replace offset paging on the resource feed with keyset
Easy · keyset pagination · composite index · sort order
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
Approach
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
Follow-up
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Design a scalable backend system for a high-traffic application.
Medium · System Design & Architecture
Approach
- Fix the numbers first: read-to-write ratio, peak versus average traffic, latency target and data size. Do the arithmetic out loud. 10 million requests a day averages about 116 per second (10,000,000 / 86,400), and the peak can be several times that.
- Keep the application tier stateless behind a load balancer so it can scale horizontally. Session data goes in signed tokens or a shared store, not in process memory. Static assets go to a CDN.
- Data layer: one primary database with read replicas, and a note that replica lag breaks read-your-writes unless those reads go to the primary. Add a cache-aside layer such as Redis with TTLs, invalidation on writes and protection against cache stampedes. Shard by tenant or user ID only once write volume outgrows a single primary, and name the cross-shard queries that sharding makes expensive.
- Move slow work (emails, exports, third-party calls) onto a queue processed by idempotent workers with retries and a dead-letter queue. Rate-limit each client with a token bucket. For a multi-tenant system that hosts applications its users build, add per-tenant quotas so one busy app cannot starve the others.
- Close on operations: track p99 latency, error rate and saturation for each tier, plan how the system degrades gracefully when the cache or a dependency fails, and say which component you expect to fail first.
Follow-up
- What breaks first when traffic grows ten times, and what do you change?
- A user writes a record and reloads straight away: how do you make sure they see it with replicas and a cache in the path?
- One tenant suddenly sends 100 times its usual traffic: how do you protect everyone else?
How would you architect a system to handle [specific constraint, e.g., high concurrency]?
Medium · System Design & Architecture
Approach
- First ask what kind of concurrency is meant: many concurrent readers, many writers contending for the same rows (counters, inventory, bookings), or many long-lived connections such as WebSockets for live editing. Each one needs a different design.
- For contended writes, use an atomic conditional update (UPDATE ... SET n = n - 1 WHERE id = $1 AND n > 0), optimistic concurrency with a version column that rejects a stale write so the client retries, or pessimistic SELECT ... FOR UPDATE. Optimistic locking suits rare conflicts. Pessimistic locking suits frequent ones, at the cost of lock waits and possible deadlocks.
- Make retries safe. Use idempotency keys stored under a unique constraint so a replayed request has no further effect. When ordering per entity matters, send all writes for one entity through a queue partitioned by entity ID so they are applied in order.
- Mind the runtime and the database connections. A Node.js event loop handles many concurrent I/O-bound requests, but CPU-heavy work blocks it, so move that work to worker threads or a separate service. Put a pooler such as PgBouncer in front of Postgres, because each Postgres connection is a separate backend process.
- Protect the system under overload with bounded queues, timeouts, circuit breakers and 429 responses that carry Retry-After. Load-test the contended path and watch lock waits and pool saturation.
Follow-up
- Two users edit the same object at the same moment: last-write-wins, operational transforms or CRDTs, and why?
- How would you detect and resolve deadlocks between two transactions that update rows in different orders?
- A single cache key receives most of the traffic: how do you stop it overloading one node?
Exports duplicate a row range about once a week
Hard · leases · clock skew · idempotency
Roughly once a week an export writes a file containing a duplicated range of rows. The affected job_run rows show attempt = 1, status = succeeded, one started_at, and a lease_owner naming a different host from the one whose logs show the job starting. Leases last 30 seconds and are heartbeated every 10 from inside the handler; lease_expires_at is computed on the worker and compared against the database's now(). Find the mechanism, and give a fix that holds even if you cannot fix the clocks.
Approach
- Start from the fact that eliminates the obvious answer. attempt = 1 means no retry was recorded, so this is not a re-run after failure; two workers ran the same row concurrently and the takeover path never touched the counter. lease_owner naming a host other than the one that started the job is the same statement from the other side.
- Enumerate the mechanisms that cause a premature takeover, then find the signal that separates them. Either the lease genuinely expired because the heartbeat did not fire, which is what happens when the heartbeat runs on the handler's own thread and the handler makes a long blocking call, or it only appeared expired because two clocks disagree, since lease_expires_at is written from the worker's clock and evaluated against the database's. The discriminator is the distribution: incidents clustered on the longest exports indict the heartbeat, incidents clustered on one host indict skew. Measure both, and measure each host's offset against the database directly.
- Read the reclaim query precisely. In PostgreSQL now() is transaction start time, not statement time, so a reclaimer holding a long transaction compares against an older timestamp than expected; clock_timestamp() is the statement-time function. This is worth ruling in or out before you redesign anything, because it changes which rows look expired.
- Remove the second clock rather than trying to synchronise it. Issue and extend the lease in the database, with lease_expires_at = now() + interval '30 seconds' in both the claim and the heartbeat, so exactly one clock is ever compared and worker skew stops mattering to this predicate.
Follow-up
- The displaced worker has already streamed half the file to object storage. What makes that side effect safe to repeat?
- You now count takeovers. What alert fires on that counter, and at what threshold?
Suggested practice checklist
Each day targets one family of reported questions: data structures and edge cases, graph traversal and refactoring, dynamic programming, then the two design questions, with the last day reserved for a full run and for talking about your own work.
Day 1 — Confirm the format and use the product
- Email your recruiter for the sequence of stages and whether each involves writing code, and note the answer.
- Build a small web application in the Bubble editor from start to finish, and note what felt slow, confusing or surprisingly capable.
- Write three engineering questions the product raised for you, for example about state in an editor or performance of user-built apps.
Deliverable: A recruiter reply on the format, a working app, and a page with your product observations and three questions.
Day 2 — Data structures from scratch and edge cases
- Implement a hash map with chaining and resizing, a singly linked list with reverse, a binary min-heap and an LRU cache in TypeScript.
- Write a test for each covering empty input, a single element, duplicates, a resize boundary and a missing key, then explain the time and space of every operation aloud.
- Write your own edge-case checklist (empty, one element, duplicates, negatives, very large input, cycles) and run each implementation against it.
Deliverable: Four tested implementations with an operation-by-operation complexity table and your edge-case checklist.
Day 3 — Graph traversal, algorithms and refactoring for speed
- Write iterative BFS and DFS on an adjacency list, then add cycle detection and a shortest-path-by-edges variant.
- Pick two further algorithms (for example binary search on the answer and two pointers) and write each with a one-paragraph explanation of the logic.
- Take a deliberately slow snippet, such as a nested loop searching an array with includes, refactor it with a Map or Set, and write the before and after complexity.
Deliverable: Traversal code with a cycle check, two explained algorithms, and one refactor with before and after costs.
Day 4 — Dynamic programming both ways
- Solve coin change and longest common subsequence top-down with memoization, then bottom-up with a table.
- Reduce the space of the bottom-up version, and note where the memoized version risks deep recursion in JavaScript.
- Write a short comparison covering which states are visited, stack depth, space reduction, and how you would pick between the two.
Deliverable: Two problems solved both ways with a written top-down versus bottom-up comparison.
Day 5 — Backend design for high traffic, plus database skills
- Outline a scalable backend for a high-traffic application: requirements, read path, write path, storage, caching, the first bottleneck and failure behaviour.
- Work the practice drill on replacing offset paging with keyset paging, since database performance tuning is a reported nice-to-have.
- Work the practice drill on holding a per-tenant cap under concurrent creates, and note what it teaches about concurrency control.
Deliverable: A one-page design with its bottleneck named, plus both database drills completed.
Day 6 — Concurrency design and explaining prior work
- Take a concurrency constraint (many simultaneous writers to the same data) and design for it: where to serialize, what to make idempotent and what to queue.
- Work the practice debugging drill on duplicated export rows to rehearse a reasoning sequence out loud.
- Prepare a diagram and spoken walkthrough of one system you built: requirements, alternatives rejected, failures you hit and what you would change.
Deliverable: A concurrency design with its trade-offs written down, a debugging write-up, and a prior-system walkthrough.
Day 7 — Full run and conversation practice
- Run one mixed session: a data structure from scratch, a dynamic programming question and a design question, talking through assumptions throughout.
- Rehearse your answer to why you want to work on a no-code platform, using what you learned on day 1.
- Work through the practice behavioral drills on disagreeing and committing, and on helping a teammate without taking over.
Deliverable: A completed mixed session with notes on where you went silent or unclear, and a rehearsed answer on your interest in the product.
Explaining your work and your fit
Candidates report that the loop includes a culture-fit conversation, technical communication and explaining the design of a past project, and that you may be asked about anything on your resume. For this role that means being able to narrate your reasoning, handle an ambiguous prompt, and give a concrete, honest account of your own work and what you want from a team.
- Walk through the system design of a project you built: the requirements, the alternatives you rejected and what you would change now.
- Pick a project on your resume and describe the hardest technical challenge in it and how you resolved it.
- Describe a time you were given an ambiguous requirement. What did you ask, what did you assume, and how did you check the assumption?
- Why do you want to build a no-code platform, and what did you learn from using the product?
- Tell me about a time you got stuck in a technical problem. How did you communicate it and get unstuck?
- Describe your working style and the team environment where you do your best work.
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?
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?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?