One of the most surprising benefits of AI-assisted coding is that it finally makes rewriting a project practical.
Back in 1970, Winston W. Royce *, in his paper Managing the Development of Large Software Systems, suggested that the only way to build a great application is to write it twice. As he put it, “arrange matters so that the version finally delivered to the customer for operational deployment is actually the second version.”
The first pass teaches you what really works and what doesn’t. The second pass is when you get it right. In the past, rewriting a full project was too expensive and time-consuming, so most teams just pushed forward with whatever they had. But with AI code generation, we can now revisit the same idea multiple times without months of extra effort, refining it each time until it’s something truly solid.
It’s like old wisdom finally caught up with the tools we have today.
* Winston W. Royce, Managing the Development of Large Software Systems is usually seen as the origin of the Waterfall method of Software Engineering
Author: William
It’s about knowing what can be done

I’ve seen quite a few posts lately talking about how important context is when using AI to code. I couldn’t agree more. For me, vibe coding isn’t about asking the AI what to code, it’s about explaining what we want to achieve. It reminds me of something I heard years ago—programming is less about knowing how to do something and more about knowing what can be done.
As a senior developer, I’ve built up a strong understanding of the tools at my disposal—React, REST APIs, SQL, and many others. I also know what I want to achieve with the products I’m developing. When I bring that knowledge into the conversation with AI, I can guide it in the right direction and ensure the tools are being used correctly. That combination feels like a genuine superpower.
I wouldn’t claim that AI makes me ten times faster, but it has changed the way I work. I’m now able to deliver far more value in the same amount of time. That shift in output and focus is where the real impact or AI Coding/Vibe coding lies.
Start Simple!

When we talk about software architecture, it’s easy to get lost in diagrams full of boxes, arrows, and buzzwords. But for most startup projects, the foundation doesn’t need to be complicated.
At its simplest, you need three layers. The front end, where users interact with your product. The back end, which includes authentication and the business logic that powers the experience. And the database, where the information lives. That’s enough to get started and build something meaningful.
And here’s the exciting part: today we can vibe code across all of these layers. Tools like Bolt.new make it quick to spin up front ends. ChatGPT or GitHub Copilot can help shape the back end and authentication logic. They can even guide you through setting up and querying your database. Beyond those, there’s a growing world of AI tools that can fit in wherever you need them.
You don’t need queues, event buses, or complex logging right away. Keep it simple, focus on delivering value, and let these tools accelerate your progress. The sophistication can always come later—when your product, team, and users are ready for it.
Vibe Coding is about Best Practices
The first part is the idea itself. It’s tempting to throw every half-baked thought at an AI and hope it will shape it into a masterpiece. But that usually leads to messy, disconnected code. A better approach is to move step by step, layering features carefully so that modules fit together naturally. Just like in traditional coding, structure and pacing matter.
The second part is the technology. AI can write React code, for example, but it won’t decide for you whether state should sit inside a component, live in a shared context, or be managed through a dedicated state manager. Understanding those building blocks is what keeps a project performant, secure, and scalable. Without that knowledge, it’s very easy to end up with something fragile.
And then there are best practices. File sizes grow too large if you never pause to split them. APIs can quickly become unmaintainable if you don’t validate authentication properly. Test coverage, error handling, naming conventions — these are the details that separate “something that runs” from “something that lasts.”
There are other pieces too: version control discipline, clear documentation, and thinking about deployment early rather than as an afterthought. All of these add up. AI coding helps us move faster, but it doesn’t replace the craft, it amplifies it. And when we respect both the creativity of the idea and the discipline of the practice, that’s when we start to see real success.
Design Patterns in React
Introduction
I am a software engineer. I love coding, and I know how to code—I’m actually quite good at it. I know how to code and I use most design patterns automatically, but I honestly do not know what they are called (with a few exceptions).
Here are some common design patterns explained with React code:
Singleton Pattern
Ensures a class has only one instance.
Example in React: A global state management instance (e.g., Redux store, Context API provider).
const StoreSingleton = (function () {
let instance;
function createInstance() {
return { user: null, theme: "dark" };
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const store = StoreSingleton.getInstance();
Factory Pattern
Creates objects without specifying the class.
Example in React: Dynamic component rendering based on props.
const ButtonFactory = ({ type, label }) => {
const components = {
primary: (label) => <button className="btn btn-primary">{label}</button>,
secondary: (label) => <button className="btn btn-secondary">{label}</button>,
};
return components[type] ? components[type](label) : <button>{label}</button>;
};
// Usage
<ButtonFactory type="primary" label="Click Me" />;
Observer Pattern
Notifies dependent objects when one changes.
Example in React: Event listeners, pub-sub mechanism, React Context API.
import { useState, useEffect } from "react";
const eventListeners = new Set();
const subscribe = (listener) => eventListeners.add(listener);
const unsubscribe = (listener) => eventListeners.delete(listener);
const notifyAll = (data) => eventListeners.forEach((listener) => listener(data));
const useNotification = () => {
const [message, setMessage] = useState(null);
useEffect(() => {
const listener = (data) => setMessage(data);
subscribe(listener);
return () => unsubscribe(listener);
}, []);
return message;
};
// Usage
const NotificationDisplay = () => {
const message = useNotification();
return message ? <p>{message}</p> : null;
};
Decorator Pattern
Dynamically adds features without modifying core code.
Example in React: Higher-Order Components (HOCs) & Custom Hooks.
const withLogging = (WrappedComponent) => {
return (props) => {
console.log("Component Rendered:", WrappedComponent.name);
return <WrappedComponent {...props} />;
};
};
const Button = (props) => <button {...props}>{props.label}</button>;
const LoggedButton = withLogging(Button);
// Usage
<LoggedButton label="Click Me" />;
Adapter Pattern
Converts incompatible interfaces for seamless interaction.
Example in React: Converting an API response to match UI needs.
const legacyAPIResponse = { fname: "John", lname: "Doe" };
const adaptUser = (data) => ({
firstName: data.fname,
lastName: data.lname,
});
const UserProfile = ({ user }) => {
const adaptedUser = adaptUser(user);
return <p>{adaptedUser.firstName} {adaptedUser.lastName}</p>;
};
// Usage
<UserProfile user={legacyAPIResponse} />;
Strategy Pattern
Encapsulates algorithms for flexible swapping.
Example in React: Using different sorting algorithms in a table component.
const strategies = {
ascending: (arr) => [...arr].sort(),
descending: (arr) => [...arr].sort().reverse(),
};
const SortableList = ({ items, strategy }) => {
const sortedItems = strategies[strategy](items);
return <ul>{sortedItems.map((item) => <li key={item}>{item}</li>)}</ul>;
};
// Usage
<SortableList items={["Banana", "Apple", "Cherry"]} strategy="ascending" />;
Command Pattern
Wraps requests as objects to allow undo/redo.
Example in React: Undo/redo for a text editor.
import { useState } from "react";
const useCommand = () => {
const [history, setHistory] = useState([]);
const [index, setIndex] = useState(-1);
const execute = (command) => {
const newHistory = history.slice(0, index + 1);
newHistory.push(command);
setHistory(newHistory);
setIndex(newHistory.length - 1);
return command.execute();
};
const undo = () => {
if (index >= 0) {
const command = history[index];
setIndex(index - 1);
return command.undo();
}
};
return { execute, undo };
};
Composite Pattern
Treats objects and compositions the same way.
Example in React: Recursive component rendering.
const Comment = ({ text, replies }) => (
<div>
<p>{text}</p>
{replies && replies.map((reply, i) => <Comment key={i} {...reply} />)}
</div>
);
Conclusion
I use most of these design patterns daily—but I don’t know what they are called. I know I don’t like a services pattern (oh sorry, the Facade pattern) as I like keeping all my fetching code together in my Context. Though, to be honest, I am starting to wonder why.
Why Are We Still Using CVs in 2025? AI Can Do Better
In a world where AI can write essays, generate code, and even hold conversations, why am I still manually sending my CV to apply for jobs? The traditional recruitment process—where a job is posted, recruiters screen candidates, and job seekers send applications—feels outdated and inefficient.
Let’s be honest: companies already struggle with an overwhelming number of applications, and job seekers waste hours tweaking resumes and searching for roles that match their skills. But what if AI could eliminate this manual back-and-forth entirely?
The AI-Powered Hiring Solution
Imagine a platform where companies upload a job description, and AI automatically extracts key requirements—skills, experience, and qualifications. On the other side, candidates upload their CVs, and AI instantly matches them to relevant job postings. Instead of job seekers manually searching through thousands of ads, AI alerts recruiters when a highly matched candidate appears.
Why This is Better for Everyone
✅ For Job Seekers: No more hours spent on job boards. You simply upload your details, and AI ensures your profile gets seen by the right employers.
✅ For Recruiters: Instead of filtering through endless applications, AI presents only the most relevant candidates, speeding up hiring decisions.
✅ For Companies: A streamlined, data-driven hiring process that reduces hiring costs and ensures better role-fit candidates.
Is This a New SaaS Opportunity?
The more I think about it, the more it feels like AI is ready to disrupt the hiring process entirely. Instead of companies and candidates spending countless hours searching for each other, why not let AI handle the matchmaking?
This could be built as a SaaS platform that integrates with job boards and applicant tracking systems, making hiring faster, smarter, and more efficient.
The question is—who’s already working on this? And if no one has done it right, maybe it’s time to build it.
My frustrations with my AI Buddies

Using AI code generation tools can feel like having a supercharged assistant—they can quickly draft code snippets, suggest solutions, and even build entire components. But as helpful as they are, I’ve found some frustrating limitations that often slow me down rather than speed me up.
One of the biggest issues I run into is that AI tools can’t remember my personal coding style. Every developer has their own way of structuring code, naming variables, and organizing logic. I might prefer concise functions or certain design patterns, but AI generators typically offer generic solutions that don’t align with how I like to write code. This means I often spend extra time rewriting or tweaking the output to match my style, which defeats the purpose of using these tools for efficiency.
Another pain point is that AI tools don’t remember which frameworks or CSS libraries I’m using. If I’ve chosen React with Tailwind CSS for a project, the AI might still suggest solutions using plain CSS or even other frameworks entirely. It’s frustrating to constantly correct the suggestions, reminding the tool that I’ve already made specific technology choices. This disconnect slows down development and forces me to double-check everything for compatibility.
I also like using the Context API in React, particularly putting fetch calls directly in the Provider for better encapsulation and centralized data management. However, AI often suggests creating standalone service files instead, which doesn’t align with my approach. This mismatch forces me to rewrite the AI-generated code to fit my preferred architecture.
Then there’s the issue of file organization. I like to use a feature-based folder structure, keeping related components, hooks, and utilities together. But AI tools have no sense of where files should go. They’ll suggest creating a new file but won’t place it in the right folder or follow my project’s organization pattern. This leads to a messy file structure that I have to clean up manually, making the process more cumbersome.
Overall, while AI code generation has a lot of potential, it lacks the memory and context needed to be truly helpful. Without the ability to learn and adapt to my style, tools, and project structure, it often creates more work than it saves. Until these tools can bridge that gap, they’ll remain more of a novelty than a reliable development partner.
My AI Buddies

Generative AI has completely changed how I approach software development. From writing snippets of code to automating repetitive tasks, these AI tools have become my go-to partners in the coding process. Today, I want to share how three of my favorite AI agents—GitHub CoPilot, Cline, and Bolt—have enhanced my workflow.
My Experience with GitHub CoPilot in VS Code
GitHub CoPilot, developed by GitHub and OpenAI, has been a game-changer for me. Integrated seamlessly into Visual Studio Code, it feels like having a helpful coding buddy suggesting lines of code and even entire functions as I type. Whether I’m working with PHP or React, CoPilot provides intelligent, context-aware recommendations that save me time and help me break through coding blocks. It’s especially handy for automating boilerplate code and exploring new frameworks or libraries without needing to constantly check documentation.
How Cline in VS Code Simplifies My Workflow
Cline is another AI-powered tool that has made coding more intuitive for me. While CoPilot helps by suggesting code, Cline enhances my development workflow by offering smarter interactions directly within VS Code. I can ask Cline to do something in a code file using English, and it can run complex terminal commands, navigate my file system, and automate build processes. This has been a huge help in bridging the gap between coding and command-line tasks, allowing me to stay focused on building instead of getting stuck on syntax-heavy commands.
Why I Love Using Bolt.new and Bolt.diy
Bolt has been a fantastic addition to my toolkit, with its two products: Bolt.new and Bolt.diy. Bolt is all about rapid prototyping. I can have an idea for a web app and turn it into a working prototype in minutes. It generates functional codebases, user interfaces, and basic logic structures, Bolt.diy van even import an existing codebase and I can tell it to add features or make modifications. It strikes the perfect balance between automation and creative control for me.
Bringing It All Together
Each of these AI tools brings something unique to my workflow:
- CoPilot boosts my productivity by suggesting relevant code in real-time.
- Cline streamlines command-line tasks, making my development process smoother.
- Bolt helps me accelerate prototyping and custom development with smart automation.
By integrating these tools into my daily work, I’ve been able to cut down on development time, dive into new technologies more easily, and focus on solving complex problems instead of getting stuck in routine tasks. Whether you’re a seasoned developer or just starting out, these generative AI agents could become your new favorite coding companions.
I truly believe the future of coding is more collaborative, with AI working alongside us to unlock greater creativity and efficiency. It’s an exciting time to be part of the software development world!
Have you tried any of these tools yet? I’d love to hear how they’re working for you!
Impact Of Ai On Software Engineering

How Generative AI is Changing Software Engineering
Generative AI is making big waves in software engineering, changing how we create, manage, and improve code. From tools that handle entire codebases to smarter ways of working, AI is becoming a key player in the field. Let’s dive into three big ways it’s transforming things: AI-managed codebases, AI-driven best practices, and the boost in productivity once developers get the hang of it.
AI-Managed Codebases: Letting AI Handle the Heavy Lifting
I can hardly contain my excitement about the rise of AI-managed codebases! The idea of tools like Bolt.new taking care of entire systems feels like stepping into a future we’ve only dreamed about. These platforms are doing things we once thought impossible, like:
- Building apps from scratch based on what users need.
- Keeping code optimized for speed, security, and scalability.
- Updating old code and fixing issues without needing a person to step in.
This is so much more than just a technical improvement; it’s a complete shift in how we think about development. Imagine having an AI partner that keeps your code clean, identifies and fixes bugs before they cause trouble, and always ensures everything is up-to-date. It’s freeing us from the tedium of maintenance and giving us more time to focus on creative and meaningful challenges. The possibilities here are limitless, and I can’t wait to see how far we can push this technology!
AI Best Practices: Developers Still Call the Shots
As incredible as AI is, it’s important to remember that developers are still the ones in charge. AI tools might be smart, but they need our guidance to make sure they’re doing the right thing. These tools are fantastic at spotting patterns and coming up with solutions, but we’re the ones who ensure:
- The code matches the project’s goals and user needs.
- Ethical issues like fairness, privacy, and security are addressed.
- The solutions work in real-world scenarios.
It’s like having an exceptionally talented assistant who can handle the heavy lifting, but we’re still the directors of the project. Developers bring creativity, vision, and judgment to the table, and that’s something AI can’t replicate. This partnership allows us to achieve more than ever before while staying true to what’s important.
A Productivity Boost: Unlocking New Potential
The speed and efficiency generative AI brings to the table are nothing short of thrilling. For developers who learn how to use these tools, the productivity gains are incredible. AI can:
- Cut down the time spent fixing bugs and testing.
- Make onboarding new team members a breeze with automatically generated documentation.
- Help developers try out new ideas quickly by handling repetitive setup tasks.
I’m amazed every time I see how much faster projects move when AI is involved. Tasks that used to take days can now be done in hours, giving teams the freedom to focus on what really matters: innovation and problem-solving. This is the kind of transformation that changes not just projects but entire industries.
But to fully embrace this potential, developers need to:
- Learn how to give clear instructions to AI.
- Carefully review AI-generated code to make sure it fits the project’s needs.
- Keep up with the latest advancements in AI tools and techniques.
The Future of Software Engineering
Generative AI isn’t just another tool; it’s opening the door to a whole new way of thinking about software engineering. It’s letting us dream bigger, move faster, and achieve more than we ever thought possible. But even with all this power, the human element remains essential. Developers are still the ones steering the ship, setting the goals, and making sure we stay on course.
The future of software engineering is incredibly exciting. We’re moving toward a world where developers spend less time on routine tasks and more time shaping strategies, solving problems, and creating bold new ideas. Generative AI isn’t here to replace us; it’s here to amplify what we can do. I’m so excited to see how this technology will evolve and to be part of this journey into a new era of innovation.
How I Define Good Application Architecture

In the ever-evolving field of software engineering, defining the attributes of good application architecture can be as subjective as it is technical. Throughout my career, I have encountered various metrics and standards used to measure the quality of software architecture. However, I believe that the most effective and perhaps the simplest test of good architecture is this: it takes fewer lines of code to create new functionality than one initially expects.
This principle might appear overly simplistic at first glance, but its implications are profound. Good architecture, in my view, inherently possesses qualities like modularity, reusability, and scalability. These features, when effectively implemented, lead to a system where adding or modifying features becomes a task less daunting than one might anticipate.
Modularity: The Cornerstone of Efficient Architecture
At the heart of this approach is modularity. A well-architected application is divided into discrete components or modules, each responsible for a specific piece of functionality. This separation of concerns not only makes the codebase more manageable but also allows for easier addition of new features. When the architecture is modular, new functionality often requires just a few lines of code to integrate a new module or to enhance an existing one.
Reusability: Doing More with Less
Reusability is another key aspect. Good architecture promotes the development of reusable components. When you can reuse existing components to create new functionality, the amount of new code required decreases significantly. This not only saves development time but also maintains consistency across the application, leading to fewer bugs and maintenance issues.
Scalability: Preparing for the Future
Scalability is integral to good architecture. An application that is scalable is designed with future growth in mind. It means that when the time comes to expand the application’s capabilities or to enhance its performance, this can be done with minimal changes to the codebase. A scalable architecture anticipates future needs, thus reducing the need for extensive rewrites or adjustments when those needs arise.
The Litmus Test of Fewer Lines of Code
Now, why do I consider “fewer lines of code for new functionality” as the litmus test for good architecture? It’s because this principle encapsulates the essence of modularity, reusability, and scalability. If adding a feature to your application requires an inordinate amount of new code, it might be a sign that the architecture isn’t as modular, reusable, or scalable as it could be. Conversely, if you find yourself pleasantly surprised at how little code is needed for new features, it’s likely a testament to the solid architecture of your application.
Final Thoughts
In conclusion, while there are many ways to assess the quality of software architecture, I find that the most telling is how efficiently new functionality can be added. A good architecture should feel like a well-oiled machine, where each part plays its role seamlessly, and improvements can be made without the need for extensive overhauls. This approach not only makes the development process more efficient but also results in software that is robust, adaptable, and future-proof.