Example-Driven Thinking
When the abstract problem is hard, construct a concrete case: three units, Alice buys two, Bob buys two at the same time. Edge cases, counterexamples and thought experiments.
"Design the inventory system" cannot be reasoned about; "three units, Alice buys two, Bob buys two at the same moment — what should happen?" can. A concrete example turns a design task into a question with an answer, and the answer usually reveals the requirement the abstract sentence was hiding.
Once one example exists, the edge cases come from varying it along a short, repeatable list: zero, one, many, duplicate, invalid, concurrent. The list is not a template — it is where the requirements the happy path hid tend to live, and each one either has an obvious answer or is a decision.
When a claim sounds right — "this always works", "that can never happen" — try to construct one case where it does not. A counterexample found in five minutes on paper is worth more than a week of confidence, and failing to find one after honestly trying is evidence, not proof.
Run the system in your head at one user, a hundred, a million — not to build for a million, but to see which assumptions hold at each size and where the design would first bend. A thought experiment is the cheapest experiment there is; it finds the assumption, and a real experiment then checks it.
Take the store from one server and one database to a size where something breaks, and watch which component is actually the bottleneck at each level — then see that a cache fixes reads and not writes, a queue fixes checkout wait and nothing else, and at low traffic every added component is unjustified. The lab runs the numbers; the lesson is how to read them.