I have been programming for about 12 years, mostly self-taught, and I am struggling to find a healthy way to use modern AI coding tools. They can generate applications, functions, refactors, and prototypes incredibly quickly, but the output often contains subtle mistakes or edge cases that are easy to miss. By the time I fully understand and verify the generated code, I could often have written the solution myself.
My bigger concern is psychological. When I write code manually, I work through each step and am forced to understand the problem. AI makes it easy to accept code that merely looks reasonable, which can weaken my problem-solving habits and make programming less enjoyable. I want to keep learning, understand the systems I build, and produce code I can trust, but refusing to use AI entirely makes me worry that I am resisting an important shift in the industry.
I have tried limiting AI to small snippets, refactoring, explanations, prototypes, and using its output only as inspiration. None of these approaches completely solves the problem, since even short generated functions can contain serious flaws. I recently showed an experienced developer a meshing algorithm I was working on, and they used an AI tool to rewrite nearly the entire thing. It appeared to fix the issue and pass the tests, but I no longer understood the design or how the bug had been addressed. Continuing with my original code would be slower, while adopting the rewrite would leave me responsible for code I could not confidently explain.
How are other programmers drawing the line? What workflows let you benefit from AI without outsourcing the design, understanding, and learning that make programming worthwhile?
5 Answers
The issue is often that too much thinking was delegated. If the tool invents the architecture, chooses the abstractions, writes the implementation, and creates the tests, reviewing the result becomes an exercise in guessing. You should still be making the important decisions: the data model, invariants, boundaries, failure behavior, performance constraints, and acceptable tradeoffs.
AI can make the mechanical part of expressing those decisions faster. It cannot remove your responsibility for knowing whether the decisions are sound. That also preserves some of the enjoyment, because you are still solving and designing the system rather than merely approving text.
Keep the design and definition of correctness on your side. Before asking a tool for implementation, build the mental model yourself, decide what the code should do, and write the specification and tests. Then ask for one small unit at a time rather than an entire application.
Tests should come from your understanding of the requirements, not from the generated implementation. Passing tests only prove that the implementation agrees with those tests; they do not prove that either one is correct. This workflow keeps you in control and makes reviewing the result much more manageable.
Use AI as a design and prototyping partner, not as an automatic production-code machine. It can be useful for exploring approaches, comparing APIs, explaining unfamiliar concepts, creating disposable demonstrations, and suggesting debugging ideas. Once you understand the direction, write the important production code yourself or rewrite the prototype into something you can maintain.
For difficult algorithms or core business logic, a large rewrite is usually a bad trade. It may fix the immediate symptom while hiding the reasoning you need to maintain the system later. The speed advantage is real, but so is the cost of inheriting code whose assumptions you cannot identify.
It is also reasonable to use different standards for different kinds of work. Disposable experiments, scripts, generated documentation, test scaffolding, and low-risk glue code can tolerate more automation. Security-sensitive code, concurrency, persistence, numerical algorithms, and central domain logic deserve much more deliberate review and often manual implementation.
You do not need to trust AI in the same way you trust a skilled colleague. Treat its output as an untrusted contribution that must earn its way into the codebase through requirements, review, tests, and sometimes benchmarking or adversarial testing. That is not anti-progress; it is normal engineering discipline.
There is no requirement to maximize raw output. Some developers enjoy designing systems and carefully shaping code, while others optimize for speed and process. Both approaches can have value, but the risks are different. If using AI makes you stop learning or makes the work joyless, reduce its role for personal projects instead of forcing yourself to use it everywhere.
At the same time, learning to evaluate and constrain these tools is becoming a useful engineering skill. The goal does not have to be choosing between writing every character yourself and accepting an entire generated application. Keep ownership of the reasoning, and automate only the parts where the verification cost is comfortably lower than the benefit.

That is the part I keep coming back to: if I do not understand the problem well enough to define the behavior and tests, I probably should not trust generated code to solve it for me.