Interview guides · Blobstation
Blobstation Software Engineer Interview Guide
Blobstation Software Engineer interview guide: reported OOP, design pattern, .NET Core debugging and project questions, plus a 7-day prep plan.
The 60-second brief
Candidates describe the Blobstation Software Engineer role as building, maintaining and scaling the systems behind the company's products, using .NET Core or other full-stack technologies. No specific product, customer base or team is named in what they report, so the stack and the engineering practices are the concrete things to prepare around. The listed responsibilities span the full development lifecycle: gathering requirements and design, implementation, testing and deployment, with code reviews and architectural audits alongside other engineers and requirement refinement with product managers.
This guide is for engineers with professional experience in .NET Core or a comparable full-stack stack who are interviewing for a Software Engineer role at Blobstation. It covers the five questions candidates report (OOP in daily work, effective design patterns, debugging in .NET Core, lifecycle model trade-offs and a recent project), the evaluation areas they list, and a plan for rehearsing spoken answers. No round order is reported, so it is organised by question type rather than by stage.
No fixed round sequence is reported. Candidates describe a mix of technical assessments and deep-dive discussions about their background, chances to meet potential peers, and a conversation with leadership, spread over a few weeks.
Focus areas
- Object-Oriented Programming (OOP)
- .NET Core
- Design Patterns
Overview
The reported questions are conversational rather than algorithmic. Four test engineering judgement: how you apply OOP in daily work, which design patterns have worked for scalable applications, how you debug complex issues in a .NET Core environment, and how software development lifecycle models trade off against each other. The fifth asks you to describe a recent project and your specific role in its success. Every answer is stronger when it is anchored in code you wrote and a decision you made, so the preparation is mostly about building a bank of concrete examples.
Candidates list several evaluation areas under technical expertise: SOLID principles and architectural patterns, nuances of .NET Core or other full-stack tooling, and advanced topics such as performance optimization, memory management and asynchronous programming. Two example scenarios are reported: refactoring a legacy module to improve its testability, and optimizing a slow database query or API endpoint. For .NET Core that means being able to talk about dependency injection, interfaces and seams for tests, async/await pitfalls, garbage collector behaviour, and how you would find a slow query (measure first, then look at the plan, indexes and the number of round trips) before changing code.
The process side carries weight too. Reports mention changing project scope, conflicting stakeholder needs, documentation habits, explaining technical ideas to non-technical colleagues, and a leadership conversation about influence and responsibility. Prepare stories that keep your contribution distinct from the team's, and be ready to defend why you picked a technology or pattern over its alternatives rather than listing what you have used. Cloud infrastructure, CI/CD pipelines and project management tools are reported as nice-to-have, so know where they sat in your last delivery process.
Where to focus your preparation
Blobstation has no reported round-by-round sequence, so the loop is covered below as one preparation block built from the reported question types and evaluation areas.
Preparation focus
Candidates do not report a fixed order of rounds. They describe technical assessments mixed with in-depth conversations about professional background, opportunities to meet potential peers, and a conversation with leadership where influence and responsibility come up. Confirm with your recruiter which stages exist, who each one is with, and whether you will write code. Then prepare two things in parallel: spoken answers to the reported technical questions (OOP, design patterns, .NET Core debugging, lifecycle models) and a project walkthrough that separates your work from your team's.
What to demonstrate
- OOP, SOLID, design pattern and .NET Core knowledge explained through your own work
- Debugging and performance reasoning, including async code and slow queries
- How you handle shifting requirements and explain technical choices to non-engineers
- Ownership of past projects, with your contribution clearly separated from the team's
How to prepare
- Ask the recruiter for the list of stages, who runs each, and whether live coding is included; if it is, practise in C#
- Write a one-page project brief: the problem, your role, two design decisions with the alternatives you rejected, one bottleneck you fixed and the result
- Rehearse spoken answers to the five reported questions, each with a real example from your own code
- Prepare two questions about how the team handles technical debt and its development process
Where candidates lose points
Defining OOP and SOLID from a textbook instead of showing them in your own code
For each of the five SOLID principles, keep one before-and-after example from a project you worked on, such as replacing a type switch with an interface and injected implementations. Say what the change made easier to test or extend.
Naming patterns without saying why you chose them or when they would be overkill
For Strategy, Factory, Decorator, Repository and Observer, prepare where each appeared in your work or in .NET Core itself (dependency injection, the middleware pipeline), the alternative you weighed, and a case where you chose not to use it.
Debugging answers that jump to a theory instead of a sequence
Practise a fixed order for a .NET Core issue: reproduce it, check logs and metrics, ask what changed, narrow the cause by bisecting, then inspect with a trace or memory dump. Include async pitfalls such as blocking on a task result and unawaited calls.
Saying "we" through the whole project walkthrough so your own contribution is invisible
Write the project story with a line for what you decided, what you built and what a teammate owned. Include one place requirements changed and how you handled the conflict between stakeholders without lowering code quality.
Treating lifecycle models as vocabulary instead of trade-offs
Compare waterfall, iterative delivery, Scrum and Kanban on how each handles changing requirements, feedback speed, documentation and release risk, then say which one your last team used and what you would change.
Questions & practice
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Canonicalise a request body into a stable idempotency fingerprint
Medium · parsing · canonicalisation · hashing
idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.
Approach
- Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
- Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
- Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
- Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.
Follow-up
- A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
- Where does the fingerprint get computed relative to request decompression and the body-size limit?
Merge partitioned event streams into one ordered feed with bounded lateness
Hard · k-way merge · watermarks · out-of-order streams
The read-model service consumes 64 log partitions carrying about 4,000 events per second in total. Each partition is ordered within itself, but partitions drift by up to 30 seconds, and the activity feed must present a tenant's events in occurred_at order. Produce the merge. State its complexity, the buffer it requires in events and in bytes, what happens when one partition is idle, and what you do with an event that arrives after you have already emitted its position. Payloads average 1 KB.
Approach
- Merge with a min-heap over the 64 partition heads keyed on (occurred_at, event_id): O(log P) per event and O(n log P) overall. The tie-break on event_id is what makes the output deterministic when two partitions carry the same millisecond, which matters because the feed is paginated and a non-deterministic order reorders pages under the reader.
- Emitting the heap head is only correct once every partition has produced everything up to that timestamp, so the emit condition is a watermark: the minimum across partitions of the highest occurred_at seen, less the allowed lateness. Events are held until the watermark passes them, which is what turns individually ordered streams into a jointly ordered one.
- Size the buffer from the lateness rather than guessing: 4,000 events per second times 30 seconds is 120,000 buffered events, and at 1 KB each about 120 MB of heap. That number is the real price of the ordering guarantee and belongs in front of whoever asked for it.
- Handle the idle partition explicitly, because it fails the feed rather than corrupting it: a partition with no traffic never advances its own maximum, so the watermark freezes and output stops entirely. Either every partition emits a periodic idle marker carrying the broker's current time, or the watermark falls back to wall clock for a partition silent beyond a threshold.
Follow-up
- The lateness budget is raised to five minutes. What is the new buffer, and what besides memory changes?
- The consumer restarts. Where does it resume from, and what does the feed look like for the first 30 seconds?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
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?
Can you explain how you apply Object-Oriented Programming (OOP) principles in your daily work?
Medium · Technical Foundations and Proficiency
Approach
- Name the four pillars, then go straight to code you wrote. Encapsulation: private fields and readonly state changed only through methods that check invariants. Abstraction: callers depend on an interface, not on a concrete class. Polymorphism: one interface with several implementations picked at runtime. Inheritance: used sparingly.
- Use SOLID as the frame for daily decisions. Single responsibility: split a class that both validates and persists. Open/closed: add a new pricing rule as a new strategy class without editing a switch. Dependency inversion: constructor injection registered in the ASP.NET Core container (AddScoped, AddSingleton, AddTransient).
- Explain why you prefer composition to inheritance. Deep hierarchies couple subclasses to base-class internals (the fragile base class problem). Inject collaborators behind interfaces, and mark classes sealed when they are not designed to be extended.
- Give a real Liskov example. Arrays implement IList<T>, but calling Add on one throws NotSupportedException. A subtype that rejects its base contract breaks callers, so you choose IReadOnlyList<T> or a narrower interface instead.
- Finish with the payoff and the cost. Interfaces let you unit-test with fakes or mocks, but an interface around every class adds indirection. You add one where there is a second implementation or a test seam, not by reflex.
Follow-up
- When would you pick an abstract class over an interface in C#, given that interfaces can have default method implementations since C# 8?
- How do you unit-test a service that depends on a DbContext or an HttpClient?
- Show me a class you refactored because it violated single responsibility. What did the split look like?
Which design patterns do you find most effective for building scalable applications?
Medium · Technical Foundations and Proficiency
Approach
- Separate two levels. Code-level GoF patterns (Strategy, Factory, Decorator, Adapter, Observer) keep a codebase changeable. Architectural patterns (cache-aside, CQRS, retry with circuit breaker, transactional outbox, publish/subscribe) let a system handle more load. Say which level you are answering.
- For each pattern you name, give the problem it solved for you. Decorator: add caching or logging around a repository through DI without touching it. Strategy: swap algorithms per tenant or per request. Adapter: wrap a third-party SDK so the rest of the code never sees its types.
- Cover the scaling patterns with their costs. Stateless services scale horizontally once session state moves to a shared store. Cache-aside cuts read load but serves stale data until invalidation and risks a stampede on expiry. CQRS separates read and write models at the price of eventual consistency.
- For calls to other services, cover retry with exponential backoff and jitter, plus a circuit breaker so a failing dependency is not hammered. Retries are only safe on idempotent operations, or with an idempotency key. The outbox pattern keeps a database write and its published event consistent.
- Name a misuse you avoid. A repository layer over EF Core can duplicate what DbContext already gives you (it is already a unit of work). A singleton that captures a scoped dependency, such as a DbContext, shares one instance across requests and is not thread-safe.
Follow-up
- Tell me about a time a pattern was overkill and you removed it.
- With cache-aside, how do you keep the cache consistent when the underlying row is updated?
- How do you stop retries from turning a slow dependency into an outage?
Can you discuss the trade-offs between different software development lifecycle models?
Medium · Technical Foundations and Proficiency
Approach
- Lay out the models by how they handle change. Waterfall runs sequential phases with requirements fixed up front. The V-model pairs each phase with a test level. Iterative and incremental models deliver in slices. Spiral is driven by risk. Agile has two common forms: Scrum (fixed-length sprints, a backlog, sprint reviews) and Kanban (continuous flow with WIP limits).
- Name the axis of the trade-off: predictability against adaptability. Waterfall gives fixed scope, budget and documentation, which suits regulated or contract work with stable requirements, but feedback arrives late and a late change is expensive. Agile gets feedback early but needs stakeholders who stay involved, and long-range dates are harder to commit to.
- Contrast Scrum and Kanban concretely. Scrum suits feature teams that plan in increments and want a regular review cadence. Kanban suits support or ops work where items arrive unpredictably, and it is measured by lead time and throughput rather than velocity.
- Tie the answer to your own experience. Say which model your last team ran, one thing it handled well, and one place it hurt, for example a scope change mid-sprint or a waterfall sign-off that froze a requirement that turned out wrong. Then say what you changed.
- Mention that CI/CD and automated tests are what make short iterations safe. Without them, short cycles just ship regressions faster.
Follow-up
- A stakeholder adds a major requirement halfway through a sprint. What do you do?
- Is there a project today where you would choose waterfall? Why?
- How did your last team estimate work, and how accurate were those estimates?
Can you describe a project you worked on recently and your specific role in its success?
Medium · Project Experience and Process
Approach
- Pick one recent project you can defend in depth, and open with the problem and a measurable goal: who used it, what was broken or missing, and what success meant (latency, error rate, release time, manual hours saved).
- Sketch the architecture in a few components, then mark your own part clearly with "I": the modules you wrote, the decisions you made, the reviews you led. Credit the team's work separately so your ownership is unambiguous.
- Go deep on one technical decision. State the options you rejected and why: a library, a data model, a sync versus async design, a pattern. Listing the stack is not enough; the reasoning behind each choice is the substance.
- Tell one hard moment as a short sequence: a bug, a performance bottleneck or a requirement change, how you diagnosed it, what you changed, and the number that showed it worked.
- Close with the outcome and one thing you would do differently. Avoid claiming results you cannot back with a number or a concrete artifact, and avoid saying "we" for the whole story.
Follow-up
- What was the hardest bug in that project, and how did you find it?
- If you started the project again, which decision would you change?
- How was it tested and deployed, and what broke after release?
How do you approach debugging complex technical issues within a.NET Core environment?
Medium · Technical Foundations and Proficiency
Approach
- Start with a reproduction and a timeline: what changed (deploy, config, data volume, a dependency), which requests fail, and since when. Use structured ILogger logs with a correlation or trace id (Activity.TraceId) to follow one failing request across services before you guess at a cause.
- Choose the tool for the symptom. In development, use the debugger with conditional breakpoints and break-on-exception. In production, where you cannot attach, use dotnet-counters (GC heap size, thread pool queue length, exception rate), dotnet-trace for CPU and event traces, and dotnet-dump for memory.
- Know the common .NET Core failure classes. Sync-over-async (.Result or .Wait() on a Task) starves the thread pool under load; ASP.NET Core has no SynchronizationContext, so the symptom is latency rather than the classic UI deadlock. Creating a new HttpClient per request can exhaust sockets, which IHttpClientFactory avoids. Leaks come from static event subscriptions or a singleton holding scoped objects.
- For memory growth, take two dumps some time apart. In dotnet-dump analyze, run dumpheap -stat to see which types are growing, then gcroot on an instance to find what keeps it alive. For slow endpoints, turn on EF Core command logging to catch N+1 queries or a missing index.
- Close the loop: write a failing test that reproduces the bug, make the smallest fix, and confirm it in the same metric that showed the problem. Then add the log or alert that would have caught it sooner.
Follow-up
- The bug only appears in production under load and you cannot attach a debugger. What do you collect first?
- Memory climbs slowly until the process restarts every few days. Walk me through finding the cause.
- How do you tell whether a fix worked rather than the load simply dropping?
Suggested practice checklist
Each day targets one reported question or evaluation area (OOP, design patterns, .NET Core debugging and performance, lifecycle models, the project story), then a final day runs them together.
Day 1 — Pin down the format and inventory your projects
- Send the recruiter your questions: the list of stages, who each stage is with, and whether you will write code or only discuss it.
- List three recent projects with your role, the stack (mark .NET Core, cloud and CI/CD pieces), one decision you owned and one bottleneck you fixed.
- Choose the project you will lead with and mark every sentence where "we" hides your own contribution.
Deliverable: A message to the recruiter with your format questions, plus a one-page inventory of three projects with the lead project marked.
Day 2 — OOP and SOLID with real examples
- For each SOLID principle, write a short C# before-and-after example, such as replacing a type switch with an interface and injected implementations.
- Prepare one example each of composition over inheritance, encapsulation protecting an invariant, and polymorphism removing a conditional.
- Speak your answer to how you apply OOP in daily work, anchored in your lead project.
Deliverable: Five before-and-after C# snippets and a recorded spoken answer to the OOP question.
Day 3 — Design patterns for scalable applications
- Write a table for Strategy, Factory, Decorator, Repository and Observer: where you used it, the alternative you weighed, and when it is overkill.
- Map each pattern to something in .NET Core you already use, such as constructor injection, the middleware pipeline or logging wrappers.
- Answer aloud which patterns have been most effective for scalability, separating code structure from runtime scaling.
Deliverable: A one-page pattern table with an example, a trade-off and a counterexample for each pattern, plus a recorded answer.
Day 4 — Debugging, performance and testability in .NET Core
- Write your debugging sequence for a production issue in a .NET Core service: reproduce, logs and metrics, what changed, bisect, trace or memory dump.
- Build a small example of an async mistake (blocking on a task result or a missing await) and write the fix and how you would detect it.
- Take a slow query or endpoint you know: record how you would measure it, what the plan or call count shows, and the fix. Then outline how you would refactor a legacy class for testability (extract an interface, inject the dependency, add tests before changing behaviour).
Deliverable: A one-page debugging runbook, an async bug with its fix, and a written slow-query or endpoint case with the measurement and the testability refactor outline.
Day 5 — Lifecycle models and changing requirements
- Build a comparison of waterfall, iterative delivery, Scrum and Kanban across requirement change, feedback speed, documentation and release risk.
- Write the story of a time scope changed or stakeholders conflicted: what you did to protect code quality and how you communicated the trade-off.
- Add where CI/CD and code review fitted into your last team's process.
Deliverable: A lifecycle comparison table and a written scope-change story with the trade-off you explained.
Day 6 — Project walkthrough and behavioral stories
- Rewrite the lead project story with your decisions, your code and your measurable outcome, keeping team work labelled as the team's.
- Practise explaining one technical decision to a non-technical stakeholder in plain language.
Deliverable: A finished project script with the I and team split marked, and spoken answers to the three practice prompts.
Day 7 — Full mock and questions to ask
- Run all five reported questions back to back out loud, with a friend interrupting for follow-ups on each.
- Since coding may be part of a technical assessment, solve the practice question on overlapping job attempts and peak concurrency in C#.
- Write two questions for your interviewers about technical debt and the team's development process.
Deliverable: A completed mock run with weak answers marked for repair, a working C# solution to the concurrency drill, and two written questions to ask.
Project, process and stakeholder stories
Candidates report that the project and process conversations carry weight alongside the technical ones, including a conversation with leadership. For a Software Engineer role on .NET Core or full-stack work, prepare stories that show what you owned, how you handled shifting requirements, and how you communicated with people outside engineering. Separate your contribution from the team's in every story.
- Can you describe a project you worked on recently and your specific role in its success?
- Describe a time you had to optimize a slow-performing database query or API endpoint.
- How would you refactor a legacy module to improve its testability?
- Tell me about a time requirements changed mid-project or stakeholders wanted conflicting things. What did you do, and what did it cost the code?
- Describe a time you explained a technical trade-off to a non-technical stakeholder. What did you leave out, and what did they decide?
- Tell me about a technology or pattern you chose over alternatives. What was the reasoning and would you choose it again?
Estimate work you have never done and defend the range
Hard · estimation · backfills · expand-contract
You are asked to estimate a change you have never attempted: add a column to a 100-million-row table, populate it, move reads across, and drop the old shape. Give a range with the assumptions that generate it, including batch size, the signal your backfill throttles on, and wall-clock hours, and name the three unknowns that would move the number most. Then describe a real estimate you gave under comparable ignorance: how you expressed its uncertainty, what you committed to, and how wrong you turned out to be.
Approach
- Decompose into independently deployable steps before estimating anything: add the column nullable, write both shapes, backfill in batches, verify, move reads, stop writing the old shape, drop it. That is four deploys spread over days, and the calendar estimate is dominated by them rather than by the loop's runtime.
- Do the arithmetic aloud for the part that has arithmetic in it: batch size times number of batches times per-batch duration, at a write rate the primary can absorb alongside roughly 1.2k writes per second of production traffic. The loop is throttled by replication lag and lock waits, not by how fast it can issue statements.
- Price the schema step by its lock rather than its statement duration. In PostgreSQL an ALTER TABLE taking ACCESS EXCLUSIVE waits for every open transaction on that table while later queries queue behind it, so a millisecond change issued during a thirty-second analytics query stalls that table for thirty seconds. Adding a nullable column with a non-volatile default avoids a rewrite from version 11; a new index wants CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block and leaves an invalid index behind if it fails.
- Express the answer as a range whose endpoints each trace to a stated assumption, then name the cheapest experiment that collapses it, which is almost always running one real batch against the real table and multiplying.
Follow-up
- How do you verify the backfill genuinely finished, given rows written by production traffic while it ran?
- Where does the backfill resume from after a worker is killed mid-batch, and what makes that resume point trustworthy?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?
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?