Software design interview guide
Every question carries the same frame: the codebase situation behind it, what it is actually testing, what a strong answer sounds like, and the flags on both sides. The red flags are specific wrong answers a confident engineer really gives — a recited principle applied where it does not hold, not a gap in vocabulary.
A service has fourteen classes and fourteen interfaces, each implemented exactly once, each named `IThing` for a class named `Thing`. A new engineer asks why, and the answer on the team is "so it is testable and loosely coupled". Meanwhile the one place with genuine variation — three payment providers — is a switch statement inside a 600-line class.
A team has an architecture review checklist whose first item is "does this comply with SOLID?". Reviews have become arguments about whether a class has one responsibility, and the codebase has grown a large number of very small classes that are individually simple and collectively hard to follow.
The module is 4,000 lines across three files. It computes invoices, applies discounts, handles proration and writes to two tables. Finance depends on its output being byte-identical for existing customers. The requested change is to add a new discount type.
A team is building a notification system that must send email now and, according to the product manager, "probably Slack and SMS at some point". The tech lead has proposed a design document listing Factory, Strategy, Observer and Template Method. No code exists yet.
An engineering manager wants a "debt sprint" every quarter. The backlog item list includes "migrate to the new ORM", "the auth module is ugly", "we still use callbacks in two files" and "the invoice generator has no tests and finance has been wrong twice".
The flags were added one at a time over two years. There are now nineteen places that read some combination of them, three that write more than one in sequence without a transaction, and a support ticket where a customer received a refund for an order that was never paid.
A service class constructs its own database client, HTTP client, clock and logger in its constructor. It has one test, which starts a real database in a container and takes ninety seconds. A new engineer has proposed adding a dependency injection container to the project.
A five-year-old order service in an older framework version. It works, it is in production, it handles about a hundred thousand orders a month, and everyone hates it. Two engineers have volunteered to rewrite it in three months. The original team is gone.
A reviewer has objected to a pull request on the grounds that it "increases coupling". The change makes the checkout module call a function on the pricing module. The reviewer suggests publishing an event instead.
A codebase is organised as `controllers/`, `services/`, `repositories/` and `models/`. Adding a field to a customer profile touches one file in each, plus a validator and a serializer. The team describes the structure as "well separated".
The renamed things include `data` to `pendingInvoices`, `flag` to `isTaxExempt`, `process()` to `chargeAndEmail()`, and `timeout` to `timeoutMs`. One reviewer says naming is bikeshedding and the diff is noise.
A payment endpoint currently throws for everything: card declined, provider timeout, invalid amount, and a null dereference in the discount calculator. The controller catches `Exception` at the top and returns a 500. Support cannot tell declined cards from outages.
A team has a `Cart` object mutated in place by fifteen call sites. A bug report says a discount is sometimes applied twice. Someone has proposed making `Cart` immutable, and someone else has objected that it will allocate too much.
A team is building an internal admin tool: twelve CRUD screens, a permissions check, and one screen that computes commission with about forty lines of rules that change quarterly. The tech lead wants entities, value objects, aggregates, repositories and domain services throughout.
An `Order` has line items, a shipping address, a payment record and a review. Loading an order currently loads all four. A report screen times out because it loads ten thousand orders to sum their totals.
A service follows a layered structure with domain, application, infrastructure and presentation packages, plus an interface and a mapper at each crossing. Adding a nullable field to a customer requires touching nine files, and every engineer on the team can recite the dependency rule.
A four-year-old monolith, one repository, one deployment, eighteen engineers on three teams. Deploys are blocked several times a week by unrelated failing tests. Leadership has asked for a microservices migration plan.