Building with AI Without Outsourcing the Thinking
A six-stage, LLM-assisted development process for specification, interface design, invariant-driven testing, implementation, sanitizer debugging, and adversarial code review

When building a project, you can mistakenly lean too far toward one of the two extremes with respect to how much you use AI.
First is being a purist, i.e. renouncing LLMs altogether and scraping documentation, subreddits, YouTube, and other such platforms for knowledge. As I have experienced, this can get very time-consuming, especially for a beginner, i.e. someone who is not familiar with this method of acquiring the required knowledge, and can become stressful when one is not able to get the answers to their questions even after spending that much time.
Second is being unhealthily reliant on AI; for example, asking for the complete implementation code from AI, reverse engineering it to understand what is happening, and mistaking the sequence of these actions for ‘building’. Such a person cannot even be considered a programmer, given that they haven’t opened a code editor, encountered a single error, or even run one of their ‘built’ implementation files.
Personally, I perceive AI as ultimately a source of thoughts, one which can take both the form of a teacher and, most importantly, an interrogator. I thus want AI to play those exact roles—question my every decision to ensure that each one is well thought out, and acquaint me with concepts or syntax which I do not already know, which are required to build the project.
Thus, driven by the desire to be thorough but not primal, I use the following 6-stage process to navigate the building:
| Stage | You do | LLM does | Exit criterion |
|---|---|---|---|
| 1. Constraint specification | State the problem in your own words — what must hold, what can be assumed, what's out of scope | Clarifying questions only — surfaces constraints you didn't state, never proposes a solution shape | You can restate the constraint list without notes |
| 2. Interface design | Draft method signatures, types, ownership — even if wrong | Adversarial critique only — names missing cases, ambiguous ownership, races the interface doesn't prevent — never supplies a corrected signature | Every critique gets answered by you revising it, not by the LLM revising it |
| 3. Invariant + fault harness | Write the invariants that must hold and the cases that would violate them, before any implementation exists | Reviews for completeness — "what fault isn't covered yet" — never writes test bodies | Fault list covers at least one case per invariant |
| 4. Blind implementation | Write the code, alone | Compiler/static-analyzer role only — names the category and location of a problem, never the fix | Compiles, and you can explain every line without the LLM having generated any of it |
| 5. Sanitizer-driven iteration | Run ASan/UBSan/TSan; propose your own fix per finding | Interprets sanitizer output only if you're stuck after your own attempt — explaining a trace is fair, fixing a race is not | Sanitizer-clean, and you can explain why each fix works, not just that it did |
| 6. Socratic defense | Answer, unprepared | Interviewer only — invents new scenarios not in the original spec, tests generalization over memorization | You defend a scenario the LLM invented on the spot, not one you'd already reasoned through |
This makes the entire process hands-on and makes sure I do not bypass the deepest, nastiest depths that programming can expose one to, and instead forces my own analytical thinking to find a way out.
About the author
- Low-level C++ engineer specializing in database storage architectures. Currently deconstructing analytical query engines and implementing LSM write paths under strict ASan, TSan, and memory-ordering constraints.
Comments
Join the discussionNo comments yet. Be the first to comment.
Building the LSM-tree Storage Engine
This series details the structural evolution of a Log-Structured Merge (LSM) tree storage engine in C++. The architecture is developed sequentially from volatile memory primitives to persistent disk storage. The implementation begins with a MemTable backed by a custom Arena allocator for localized memory control. Concurrency is established via a lock-free SkipList. Data durability is enforced through Write-Ahead Logging (WAL) protocols. The system finalizes by flushing volatile data structures to immutable Sorted String Table (SSTable) blocks on disk. The documentation traces the exact engineering progression required to assemble high-throughput database internals from the ground up.





