Interview guides · BaseCamp Franchising
BaseCamp Franchising Software Engineer Interview Guide
BaseCamp Franchising Software Engineer interview guide: reported C#, EF Core, React, MobX and PostgreSQL questions plus a 7-day prep plan.
The 60-second brief
BaseCamp Franchising's engineering work supports the Uptown Cheapskate and Kid to Kid resale brands, whose stores are franchised (the reported role description mentions close to 280 of them). The Software Engineer role maintains and extends an in-house software suite that covers point-of-sale, appraisal of items and inventory. Candidates describe the work as feature delivery and bug fixing across the whole stack, run in the Shape Up development style, alongside QA staff, UX designers and product managers, plus helping front-line teams with bugs and production incidents. The listed stack is C# on .NET 10, React, TypeScript and MobX, with PostgreSQL; Redis, ClickHouse and DigitalOcean appear as nice-to-have experience, and OTel is named for observability.
This guide is for experienced web engineers (the reported requirements ask for 6+ years of building web applications) preparing for the BaseCamp Franchising Software Engineer loop. It covers the five reported questions: your C# and EF Core production experience, testing complex React applications, managing state with MobX in a large TypeScript project, PostgreSQL and Redis performance, and hybrid hosting. It adds a plan for explaining your own verification routine, your ownership of features, and how you would handle the stack items you have not used. Candidates describe the role as on-site in North Salt Lake, Utah.
Candidates describe a conversation-led process built around deep dives into your technology stack and past projects, with no traditional live coding reported recently. No round count, order or length has been reported, so confirm the sequence with your recruiter.
Focus areas
- C# (.NET 10)
- Web Application Development
- React
Overview
The role is full-stack maintenance and growth of one proprietary suite rather than greenfield work. That shapes what is worth preparing: how you keep a long-lived C# and EF Core codebase fast and correct, how a React and MobX front end stays understandable as it grows, and how you find and fix a slow PostgreSQL query. Candidates also report observability (OTel) as an evaluation area, so be ready to say what you would instrument and why.
All five reported questions ask how you work in this stack instead of posing a puzzle. Two of them (your C# and EF Core production experience, and hybrid hosting) invite a story from your own history. The other three (testing React applications, MobX state in a large TypeScript project, PostgreSQL or Redis performance) invite a structured explanation of your method with examples. Prepare each as a short claim, a concrete example with numbers from your own work, and what you would do differently now.
Several themes recur in what candidates describe: producing high-quality code on your own, explaining how you check your own work by hand before it ships, and communicating clearly in writing and aloud. The requirements candidates quote also ask for work done without LLM assistance, so rehearse explaining past decisions from memory and reasoning, not from generated text. Read about the Shape Up method before the conversation so you can discuss scoped work cycles, and be honest about tools such as ClickHouse that you have not used: say what you have used that is similar and how you would learn it.
Where to focus your preparation
No round sequence has been reported, so this guide treats the loop as one preparation stage built around the reported stack questions; confirm the real sequence with your recruiter.
Preparation focus
No round names, count or order have been reported for BaseCamp Franchising. What candidates do report is a conversation about your technology stack and past experience, with traditional live coding not featuring in recent accounts. Treat the whole loop as a stack deep dive: for each listed technology, have one real project, one decision you made, and one thing that went wrong. Since format can change, ask your recruiter whether any stage involves writing code, who you will meet, and whether a written exercise is included.
What to demonstrate
- Depth in the reported stack: C# .NET, EF Core, React, TypeScript, MobX and PostgreSQL
- Quality of your testing and manual verification of your own work
- Clear structured communication of past technical decisions
- Awareness of observability and reliability concerns, with OTel named in the reported evaluation areas
How to prepare
- Write a one-line inventory per listed technology: where you used it, your role, and one measured result, and mark honestly what you have not used
- Rehearse aloud the five reported questions, each as a claim, an example with numbers, and a lesson
- Write down the steps of your own manual verification routine before you call a change finished
- Ask the recruiter whether any stage involves writing code and what the sequence is, then adjust the plan
Where candidates lose points
Describing EF Core experience as 'I used it for data access' with no query, migration or tracking detail
Prepare two concrete cases: an N+1 or over-fetching fix with before-and-after query counts, and a migration you applied safely. Be able to say when you used AsNoTracking, projections or split queries and why.
Answering the React testing question with tool names only (Jest, Testing Library) and no strategy
Explain what you test as a unit (pure logic, a MobX store without rendering), what you test as an integration (a component tree with a mocked network layer), what you leave to manual verification, and one test that caught a real bug.
Claiming MobX or Redis depth you cannot back up under follow-up questions
Mark each stack item honestly on your inventory. For items such as ClickHouse that you have not used, name the closest system you have run and how you would learn it. Candidates report that honesty about the stack is expected.
Presenting past work in a way that leaves it unclear what you did, or leaning on generated code you cannot explain
Use 'I' for your own decisions and name the teammates for theirs. Be able to reconstruct the reasoning for any code you mention without help, since the reported requirements ask for independent work without LLM assistance.
Treating testing as unit coverage only and skipping how you verify a change by hand
Write a short routine you can recite: reproduce the bug, check the happy path and two edge cases in a running environment, check the database effect, and confirm logs or traces. Candidates are told to be ready to explain this process.
Questions & practice
Diff a projection against the primary without per-row point reads
Hard · reconciliation · range hashing · throttling
The listing projection has drifted and some rows show a stale version. The primary holds 40,000,000 resource rows across 12,000 tenants while serving 1,200 writes and 14,000 reads per second. The obvious repair, reading each resource row and comparing its version against the projection, is correct and would eventually finish. Explain precisely why it is unacceptable here, then give a diff that finds the differing rows, state its complexity, and make it safe to run against a live primary. Replication lag is usually under 100 ms and is not bounded.
Approach
- Quantify the naive cost rather than calling it slow: 40,000,000 point reads at even 0.5 ms each is over five hours serialised, and the only lever is concurrency, which is exactly what you cannot spend. The primary's pool is sized for the write path, and 40,000,000 random reads evict the buffer cache that sustains the 85 percent cache hit rate, so the audit degrades the system it is auditing.
- Replace random access with one ordered pass per side. Both sides can be read in (tenant_id, resource_id) order, which is a sequential scan on each and a merge join in O(n) time and O(1) memory. For a dense diff that is the whole answer, and it reads the primary once instead of 40,000,000 times.
- For the expected sparse case, compare range hashes instead of rows: partition the key space, compute per range an order-independent aggregate over hash(resource_id, version), compare aggregates, and descend only into ranges that differ. With d differing rows and branching factor B, at most d ranges mismatch per level, so the drill-down examines O(d log_B(n/d)) ranges and reads full rows only in mismatching leaves.
- Aggregate with a sum modulo 2^64 or a multiset hash, never XOR. XOR is order-independent but self-cancelling, so two rows wrong in the same way, or a row duplicated on one side, leave the range aggregate matching and the range is declared clean.
Follow-up
- The diff reports 900 stale rows. How do you decide between patching those rows and rebuilding the projection from resource_revision?
- Same job, but the projection lives in a search index that cannot be scanned in key order. What changes?
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?
Track a rolling failure rate per destination for circuit decisions
Easy · sliding window · ring buffer · circuit breaker
The egress service delivers about 1,500 webhooks per second across roughly 40,000 destinations, each call bounded by a 10 second timeout. Maintain, per destination, the failure rate over the trailing 60 seconds so a caller can ask before dispatch whether the circuit should open. Attempts arrive as (destination_id, finished_at_ms, outcome). Requirement: amortised O(1) per attempt, with total memory bounded by the destination count rather than by traffic. Give the structure, its exact memory, and the rule that stops a destination with three attempts from opening a circuit.
Approach
- Name the exact-deque version and then reject it as the default. Holding timestamps and advancing a tail pointer past anything older than now minus 60 seconds is a correct two-pointer window at amortised O(1) per attempt, but its memory tracks in-window traffic, so one destination in a retry storm holds hundreds of thousands of entries while thousands of quiet destinations hold none.
- Use a ring of 60 one-second buckets per destination, each bucket a pair of counters for attempts and failures. On an attempt, advance the ring by the elapsed whole seconds, zeroing at most min(elapsed, 60) buckets, then increment the head. That is amortised O(1) with a fixed footprint per destination.
- State the footprint: 60 buckets times two 4-byte counters is 480 bytes of payload per destination, so 40,000 destinations is roughly 20 to 25 MB with per-entry overhead, bounded by the catalogue rather than by the rate. The cost is granularity, since the oldest bucket ages out in whole seconds, which is far tighter than the decision needs.
- Require a minimum sample before the circuit may open. A destination with three attempts and three failures reads as 100 percent and is not evidence; a floor of roughly 20 attempts in the window makes the ratio meaningful, and below that floor use a run of consecutive failures as the trigger instead.
Follow-up
- The fleet is 30 instances and each sees roughly a thirtieth of a destination's traffic. Where does the rate actually live, and what does a per-instance answer get wrong?
- A destination answers in 9.5 seconds and succeeds. It is not failing but it is consuming your per-destination concurrency. What signal should open the circuit here?
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?
Keep soft-deleted accounts from blocking re-registration
Easy · soft delete · unique constraints · engine differences
app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.
Approach
- Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
- Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
- For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
- State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.
Follow-up
- A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
- How do you honour an erasure request while resource_revision.actor_user_id still references this table?
Can you explain your approach to writing unit and integration tests for complex React applications?
Medium · Technical & Domain Expertise
Approach
- Split the suite by what each layer proves. Plain Jest or Vitest unit tests cover pure logic with no DOM: stores, formatters, price or validation rules. Component tests use React Testing Library and render real children. A few Playwright or Cypress end-to-end tests cover only the flows that must never break, such as a checkout or inventory update.
- Test what the user sees. Query with getByRole or getByLabelText, not test ids or class names. Drive input with userEvent, not fireEvent, because userEvent fires the whole event sequence a real user produces. Assert on rendered output, never on component state or hook internals, so a refactor does not break the test.
- Mock at the network boundary, not inside components. Mock Service Worker (MSW) handlers return realistic responses, and integration tests render a whole page with real stores against those handlers. Each test overrides one handler to return a 500, an empty list or a slow response.
- Handle async without flakiness. Use findBy* queries or waitFor for content that shows up after a request, and fake timers for debounced inputs. Create fresh store instances and reset MSW handlers in each test so no state leaks between tests. Do not let snapshot tests carry the main assertion.
- Cover the states that tend to go missing: loading, error, empty, permission-denied and a double-submit on a slow request. Explain how you pick what goes to end-to-end tests: the cost of a regression in production compared with the run time and flakiness the test adds to CI.
Follow-up
- How do you test a component that reads from a MobX store without wiring up the whole app?
- When is it right to mock a child component, and what do you lose by doing it?
- Your integration suite takes 15 minutes and fails at random about once a day. How do you find and fix the flaky tests?
How have you managed state in a large-scale TypeScript project using MobX?
Medium · Technical & Domain Expertise
Approach
- Describe the store structure. Each domain area gets its own store, for example inventory, cart or user session. A RootStore builds them and passes references between them. UI state such as open dialogs and form drafts stays apart from domain data. Stores reach components through React context, not module singletons, so each test can build a fresh instance.
- Be precise about MobX 6 APIs. Call makeObservable(this, {...}) in the constructor with observable, computed and action annotations, or use makeAutoObservable. makeAutoObservable cannot be used on a class that has a superclass or is subclassed. Make every mutation an action; the default enforceActions setting of 'observed' warns when observed state changes outside one.
- For async work, state updates after an await are no longer inside the original action. Wrap them in runInAction, or write the method as a flow generator, which also supports cancellation. Keep loading and error state on the store next to the data so every component reads the same status.
- Derive values instead of copying them. Totals, filtered lists and validity belong in computed values, which stay cached while something observes them. Wrap components in observer and read observables as late as possible: pass the object down and read its fields in the child, so only the child re-renders. Use <Observer> inside render callbacks of third-party components, because reads there are not tracked otherwise.
- Manage side effects and the trade-offs. Every reaction or autorun returns a disposer; call it in a useEffect cleanup or the store's dispose method to avoid leaks and double subscriptions. Keep entities normalised by id in an observable.map, and use toJS before sending data to an API. Compared with Redux there is less boilerplate, but tracking is implicit, so trace() and the MobX devtools are how you explain an unexpected re-render.
Follow-up
- A list of 2,000 rows re-renders completely when one row's quantity changes. How do you find the cause and fix it?
- How do you handle a request that resolves after the user has left the screen, or two overlapping requests that finish out of order?
- How do you unit-test a store that depends on two other stores and an API client?
What is your strategy for optimizing database performance in PostgreSQL or Redis?
Medium · Technical & Domain Expertise
Approach
- Measure before changing anything. Use pg_stat_statements to rank queries by total time, not just by the slowest single call. Run EXPLAIN (ANALYZE, BUFFERS) on the top ones and look for sequential scans on large tables, estimated rows far from actual rows (stale statistics: run ANALYZE), nested loops over large row counts, and sorts that spill to disk because work_mem is too small.
- Fit indexes to the query shape. In a composite B-tree, put equality columns first and the range or ORDER BY column last. Use a partial index for a hot subset such as WHERE deleted_at IS NULL, an expression index for lower(email), and INCLUDE columns for index-only scans, which also need a visibility map kept current by vacuum. Every index adds write cost and disk, so remove unused ones (idx_scan = 0 in pg_stat_user_indexes).
- Fix the access patterns that EF Core produces. Find N+1 queries from lazy loading and replace them with Include or, better, a Select projection of only the needed columns. Use AsNoTracking for read-only queries. Use AsSplitQuery when several collection Includes multiply rows. Read the generated SQL with ToQueryString or EF logging. Replace OFFSET pagination on deep pages with keyset pagination (WHERE (created_at, id) < (...)).
- Use Redis as cache-aside. Read Redis first; on a miss, load from Postgres and SET with a TTL. On writes, delete the key instead of updating it, which narrows the race window. Add TTL jitter or a short lock so a popular key's expiry does not send a burst of requests to Postgres. Set maxmemory-policy to allkeys-lru for a pure cache.
- Redis is single-threaded for commands, so one slow command blocks every client. Never use KEYS in production: it is O(N) over the keyspace, so use SCAN. Watch for large keys (redis-cli --bigkeys) and hot keys, use pipelining or MGET to cut round trips, and check SLOWLOG. Then cover connection limits: Npgsql pools connections per process, and many app instances may need PgBouncer in front of Postgres.
Follow-up
- A query is fast in staging and slow in production with the same code. What do you check first?
- A sale updates stock in Postgres while the stock count is cached in Redis. How do you keep a store's screen from showing a stale count?
- When would you decide not to add an index, or remove one, even though a query would use it?
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
The plan follows the five reported questions and the stack the role lists, then adds ownership and Shape Up preparation. Because no round sequence is reported and live coding is not reported in recent accounts, most days are spent on spoken explanations backed by your own examples.
Day 1 — Confirm the format and inventory the stack
- Write questions for your recruiter: the sequence of stages, whether any stage involves writing code, who you will meet, and what format a stack discussion takes.
- Build a table with one row per listed technology (C# .NET, EF Core, React, TypeScript, MobX, PostgreSQL, Redis, ClickHouse, DigitalOcean, OTel) with your project, your role, one measured result and an honest 'not used' mark.
- Read a summary of the Shape Up method and write five bullets on how scoped work cycles change how you plan and cut work.
Deliverable: A recruiter question list, a stack inventory table and five Shape Up bullets.
Day 2 — C# and EF Core production story
- Choose one production .NET system and write the story: context, your role, three decisions, one measured result.
- List the EF Core specifics you can defend: DbContext lifetime, migrations in deployment, AsNoTracking, projections, Include versus split queries, concurrency tokens, and a case for raw SQL.
- Answer aloud, then rewrite the weakest part.
Deliverable: A one-page story outline for the C# and EF Core question, plus a spoken run-through.
Day 3 — React testing and MobX state
- Take a complex component you own and write its test plan: which logic is unit tested, which flows are integration tested with a mocked network layer, and which risks stay manual.
- Describe how you structured MobX stores in a large TypeScript project: observables, computed values, actions, store boundaries, and how you test a store without rendering.
- Answer both questions aloud and note any term you hesitated on.
Deliverable: A test plan for one real component and a store-structure outline, each rehearsed aloud once.
Day 4 — PostgreSQL and Redis performance
- Take one slow query (real or invented orders table) and run EXPLAIN (ANALYZE, BUFFERS); write what the plan shows and which index, composite, partial or covering, fixes it.
- List what you would cache in Redis for an inventory-style workload: key design, expiry, invalidation, and how you prevent a stampede.
Deliverable: An annotated query plan before and after an index, a Redis caching sketch and solved practice questions.
Day 5 — Hybrid hosting, observability and incidents
- Draw an environment map for a system you know: which parts run where, how they communicate, where secrets and configuration live, and what happens when a link fails.
- Write what you would instrument with OTel at a boundary: traces, request IDs in logs and the metrics you would alert on.
Deliverable: An environment diagram with failure handling noted, an instrumentation list and a written diagnosis sequence.
Day 6 — Ownership, verification and reversed decisions
- Write the feature you owned from design to deployment: the decisions, who you worked with, what you tested by hand and the outcome.
- Write your manual verification routine as a checklist you can recite.
Deliverable: One ownership story, a manual verification checklist and one reversal story, all with numbers or concrete outcomes.
Day 7 — Full dry run without assistance
- Answer all five reported questions aloud in one sitting without notes or any generated help, then mark every answer that lacked an example.
- Fix the weakest two answers and rehearse them again.
- Write the two questions you most want to ask the team about their release cycle and quality practice.
Deliverable: A completed unaided dry run, two revised answers and two questions to ask.
Ownership, verification and hosting stories
Candidates describe a conversation centred on your past projects and technical decisions, with ownership, working through ambiguity and clear communication named as themes. Prepare stories where you made the decision and can say what it cost or achieved.
- Describe a feature you owned from design to deployment, and what you did to verify it by hand before and after release.
- Tell me about a time you had to solve a problem independently with unclear requirements, and how you decided when to ask for help.
- Describe a technical decision you made and later reversed: what you believed, the evidence that changed your mind, and what the reversal cost.
How would you describe your experience with C#.Net and EFCore in a production environment?
Medium · Technical & Domain Expertise
Approach
- Treat it as an ownership story rather than a CV skim. Pick one production system, say in two sentences what it did and your role, then spend most of the answer on three or four decisions you personally made in C# and EF Core. Use numbers from your own work only.
- Name the EF Core details you actually handled: DbContext lifetime (scoped per request), how migrations were generated and applied during deployment, AsNoTracking on read-only paths, Select projections instead of loading whole entities, Include versus AsSplitQuery for large collection loads, and concurrency tokens for optimistic concurrency.
- Give one problem with evidence. For example, an N+1 pattern found by logging the generated SQL (LogTo or query logs in a non-production environment), the query count before and after, and the index or projection that fixed it. State what you measured and how you confirmed no regression.
- Cover the operational side: async/await used correctly with cancellation tokens, dependency injection setup, how a bug in production was diagnosed, and how changes were tested. If you have not used .NET 10 itself, say which version you used and what you know about moving up.
- Close with what you would do differently. The mistake that sinks this answer is a list of technologies with no decision, outcome or trade-off attached.
Follow-up
- How did you find and fix a slow or N+1 EF Core query, and how did you prove the fix?
- How do you apply a migration to a database that is serving live traffic without breaking the running version of the app?
- When did you choose raw SQL or a lighter data access tool over EF Core, and why?
How do you handle the challenges of integrating or hybrid hosting environments?
Medium · Technical & Domain Expertise
Approach
- The wording is open-ended, so start by defining it: hybrid hosting means parts of one system run in different places, such as on-premises and cloud, or across two hosting providers, and integration means those parts must talk reliably. Say which situation you have lived. If you have no direct experience, say so and reason from principles instead of inventing a story.
- Name the concrete problems and your handling of each. Network failure and latency across the boundary: timeouts, retries with backoff, idempotency keys, circuit breakers. Configuration and secrets: one secret store, no hand-edited per-environment settings, the same build artifact promoted through environments. Data consistency: a single writer per dataset, a queue or outbox for cross-boundary writes, and a reconciliation job.
- Cover observability across the seam: propagate trace context between environments with OpenTelemetry, correlate logs by request ID, and alert on the boundary itself (error rate and latency of cross-environment calls), since the listed stack names OTel.
- Explain rollout and rollback: backward-compatible changes in an expand-then-contract order, feature flags, a staging environment that reproduces the boundary, and contract tests between the two sides. State what broke once and what you changed afterwards.
- Avoid a generic checklist. A strong answer picks one real integration, states the decision you made, and gives the measurement or incident that justified it.
Follow-up
- What happens to in-flight work when the link between the two environments goes down?
- How do you keep configuration and secrets consistent across environments without copying values by hand?
- How would you trace one request end to end when it crosses both environments?
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?