First and foremost, this isn’t a post that is critical of Cursor or “Vibe Coding.” In fact, it’s far from it.
I absolutely love Cursor. It has been, and continues to be, my go-to IDE for the past six months, supercharging my productivity and allowing me to ship features at an unmatched pace.
I’m such a huge fan that I not only recommended it to my entire team but also got the company to cover the subscription costs.
There are only a few things in the world that bring us absolute joy, and building cool shit is one of them. Anything that fosters that excitement is worth celebrating.
And as for Vibe Coding with AI, I absolutely encourage it for anyone new to programming. The sheer joy of creating something from scratch is unparalleled, and the whole “Vibe Coding with AI” trend makes that experience accessible to all.
This is more of a cautionary note, especially to the junior developers and more serious builders, who is considering a career in software development.
Every one else, please ignore and continue building random shit that brings you joy.
Throughout human history, our progress has been powered by a relentless drive to build things and to create tools that extend our capabilities of building those things.
From the simple stone axe of our ancestors to the invention of the printing press, From gun powder to massive aircraft carriers, each breakthrough was sparked by the drive to extend our mere human capabilities, to ascend into super human level of capabilities to achieve super human results.
The ability to create something out of nothing is what has always defined the spirit of human ingenuity.
I have been a software engineer for almost a decade, and a coder for more than twice of it. And I’ve absolutely loved each and every second of it.
I got my first computer when I was 12 years old. An Intel core2duo machine connected to a bulky CRT monitor. I still remember the exhilaration I got when I ran my first C program, writing the code directly into vim and compiling it with gcc on the terminal. Over the years, I went through many different such tools and packages.
Even to this day, every time I use a new tool that drastically changes the way I approach a problem my eyes light up like that 12 year old, perpetually curious, forever starry eyed and in love with technology.
I had a similar moment when I first started using Copilot and then Cursor. I could just tell what I wanted and it’d magically create code to do it. And I took maximum advantage of it, was shipping out code non stop, moving at an unparalleled building speed like never before in my career.
That was until the dream run came to a sudden grinding halt about a month ago.
I was trying to debug a sudden convoluted issue in my code base. I’m generally good at debugging and a lot of it has to do with the fact that I truly believe in building Software systems than just Software programs.
And well designed, well defined systems run like a well oiled machinery. and moreover, It’s easier to debug because you can see the side effects of an issue from a mile away.
But this issue, which was actually just an inverse conditional mishap inside a while loop in a utility function took me a few excruciating hours. This was not directly part of the current file and hence it was not being picked up by cursor in it’s context, and hence it was unable to catch the error.
What pissed me off so much tho was my dependency on Cursor, that I only started looking through the code much later, trying out to figure out the bug, something I should have done right away and which would’ve let me fix it in a few minutes.
But more than wasting my time on catching the bug, while sifting through the code base, I realised that a lot of patterns that I’d otherwise religiously follow, like modularity, reusability and composability was nowhere to be seen in my code base.
Frankly, I felt a bit disappointed cause the code, while it did it’s job, was a lot sloppier than it should with a lot of volume, huge functions, lots of duplicated code, no proper isolation at places or segregation of responsibilities where it was needed.
It was a little painful to look at my code.
And that’s when in the first time in six months that I realised, Fuck, I might actually be regressing as a Software Engineer, And sucking even more as a Systems Engineer. The realisation was a hard hit, which soon then snowballed into all sort of revelations. While I’ve been moving at an unparalleled pace, building things like never before, my learnings had actually been plateaued.
I’ve not really thought hard about something or learned something new in so long.
And then it got me thinking about all the junior devs that were working with me. And while I see them producing so much more output than what I was building at that stage in my career, are they really learning just as much? Are they understanding the differences between multiple approaches deeply enough so that they can make systems that are scalable and robust enough to withstand the stress of sudden growth or rapid change?
Honestly, I was not sure.
And that lead me to scrutinise our code and our technical documents more deeply, and the realisations were not pretty. A lot of the solutions on its own were AI generated slop with lesser innovations and bold approaches. While we did do a whole lot of new things as a company, a lot of it was just traditional solutions which could’ve been designed a bit more elegantly. There was a whole lot of code, but lesser systems and modules.
It was like we were ignoring one of the fundamental tenets of Software development, which is to write as less code as possible.
So over the past few weeks, I spend time trying to figure out what are some best practices to follow while building with AI. So that we don’t fall into certain patterns of convenience that produces sloppy code that just about does the job.
Most of these have been said many times before by experienced builders, I’m just reiterating them here with intention for the AI assisted software development.
Systems first, Software second.
A good engineer should never shy away from complexity, but should also internalise that there is nothing really valuable in creating a system so complex that it gives a headache to anyone who tries to make any sense of it.
Real art in Engineering lies in Absolving systems of their complexities.
I would even go as far as to say that it is Art. And that’s where your actual core skills as a developer lie.
A System becomes better not when you add more complexity to it, But when you take away the complexity but it still works the same, or better.
Modularise your workflows, logically arrange your code base into modules.
Build components within modules, let them interact with each other for the outcome you aim to achieve.
Create true composability in your codebase. Don’t just look at things in terms of inputs and outputs that go through a black box.
An engineer should be fascinated by what goes on inside a system and what makes it tick
While working with an AI IDE like Cursor, this means you need to carefully supply instructions on how you want your code to be generated.
Give examples using context files. Explicitly instruct it to modularise the code and reuse it in a certain way. Manually design your interfaces and facades. Just use AI to generate the final code output as per your well designed, well thought out pattern.
Solution-ing? Do it the first principles way
You’re as good an engineer as how you’re at Problem solving. And the a good problem solver is always a cross functional thinker, someone who always thinks from First principles. When you ask AI for a solution, what you get may not be the best solution, but is something of an approximation of what would work in most cases, which honestly, is almost always a good enough solution,
But when has “good enough” been enough for high achieving people?
Think from First principles always, approach the problem from multiple axes.
Formulate solutions in your head and write it down, Use AI more as an assistant rather than a teacher. Use it as a knowledge base rather than an all knowing source of truth.
Come up with N different solutions and compare them, truly try to understand the differences and nitty gritties of different approaches before taking a decision. Learn along the process.
Don’t just ask AI to give you a solution for this problem.
I’ve seen a lot of cases where AI is told the problem statement and is asked to generate an PRD/ERD for it. Don’t do it.
Use ChatGPT/Claude/Gemini as a tool where you can bounce your ideas off of. Even when you’re stuck and you need a solution, If you ask for a solution, ask why it’s the best solution for the situation.
Force yourself and the model to think through your own reasoning capabilities rather that what was RLHF’d into it.
Less code, the better.
Writing code has always been the easiest part of the job. Sure it used to take time, but it was still easy because it was the most direct part of the whole job. Code is what Code does and as soon as know what you want to do, Writing it down in a language of your choice is much like translation. You would have to look up the syntax every now and then, but it was mostly quite straight forward.
But writing beautiful code is hard, writing elegant code that mesmerises anyone who looks at it is even harder
Break your code down into simple, elegant functions that works very well together like a well oiled machine.
Periodically go through your code base to understand redundancies, use AI to intentionally refactor your code as per best practices.
Avoid duplication, reuse functions by creating utility files, follow a pattern in your entire code base end enforce your code generator to follow it.
And that’s what you do when you truly love programming, You keep iterating, you keep understanding nuances and making it better. You find joy in knowing about the little syntactic sugars in different programming languages. And you do it all not because someone asked you to, but because you appreciate it yourself.
And that’s something every software engineer should be doing, and not just writing more and more code
Take pride in understanding your codebase like the back of your hand.
AI generated slop is now recognisable to me from a mile away. A lot of my peers also share the same view.
I’m sure, with time it’ll get better, but for the time being, it’s quite off putting for me to see it in production scenarios.
As builders, we owe it to ourselves and our stakeholders to maintain a certain standard to our craft.
So to new Software engineers, and serious builders,
Don’t just generate code for it’s utility, try to understand what it does and how it does it.
Figure out patterns, try to refactor the code to make it more simple and readable.
Learn about design patterns and systems engineering, make an informed decision about what to use when and why to use it.
Go back to first principles thinking for solving a problem.
Build things, Break things, But do it the right way. I know it’s hard, but come on, if it was that easy, what even is the point of doing it? :)
How much thinking is done before the first line of code? What tools are useful in system design? Do you flowchart the functions digitally before you create them?