I have about seven years of formal IT education—four years of secondary school and three years studying for a bachelor's degree. I've been exposed to many areas of technology, but because I haven't used much of that knowledge recently, I often feel rusty and behind.
During university I worked on a wide range of projects, including full-stack applications, relational and NoSQL databases, multiplayer games, web frontends, REST APIs, a programming-language interpreter, a web server, and various data-structure implementations. However, I'm not confident I could recreate many of those projects today without starting from scratch.
Game development has always been my main hobby, but I regularly struggle with organizing projects, maintaining motivation, and sticking with one idea instead of jumping to another. I'm also interested in contributing to free and open-source software, although I'm unsure whether I have enough practical knowledge to be useful.
When starting a project, I often run into architectural problems I don't know how to solve. Sometimes I worry about scalability; other times I don't know how to design the solution at all. Eventually I abandon the project because the design doesn't feel correct. I know concepts such as SOLID, but I still have trouble judging whether an approach is appropriate.
Perfectionism makes this worse. Advice like "build a prototype first" or "make it work, then refactor" feels overwhelming because I imagine having to rewrite an entire module later. In school, I often spent so long trying to make assignments feel right that I submitted them unfinished or rushed. Even when instructors said I understood the material well, I rarely felt confident myself.
I suspect my main problem is that I don't have a consistent, step-by-step process for designing and building software. What principles or techniques do you use when starting a project? Do you follow a general workflow or feedback loop? How do you plan and divide work effectively?
More broadly, how do you become confident in a solution while accepting that the code will never be perfect?
5 Answers
You gain confidence less from believing your architecture is flawless and more from being able to verify the software quickly. Define the behavior you need, implement a small part, test it, and get feedback from the result. This can involve unit tests, integration tests, prototypes, or simply having someone use the feature. Repeated feedback and course correction are more dependable than trying to predict every future requirement.
The most important practical step may simply be continuing after the first obstacle instead of treating it as proof that the project is fundamentally wrong. Pick a small project with a clear scope, write down the next concrete task, and allow yourself to make temporary decisions. Open-source contribution can be approached the same way: start with documentation, tests, small bug fixes, or improving an existing issue. You don’t need to understand an entire project before making a useful contribution.
Design patterns can help when you recognize a recurring problem, but they aren’t a universal recipe. For example, if an application may eventually send email, text, and push notifications, you could define a common notification interface and provide separate implementations for each delivery method. The rest of the application can depend on that interface rather than the details of sending a message.
However, adding an abstraction before you need it can create unnecessary complexity. If the application only needs one kind of notification, a direct implementation may be the better design. Start with a working proof of concept, then introduce abstractions when changing or extending the code becomes genuinely difficult. Automated tests and other quick feedback mechanisms also make it easier to judge whether a change is safe.
Professional development tends to prioritize delivering useful increments over designing the perfect system upfront. Work is usually divided into small pieces that can be completed in a short cycle—perhaps laying the foundation for a feature, adding a specific interaction, or implementing one behavior. After each increment, you learn more about the problem and improve the design where it actually matters. “Good enough for now” is often how a robust system is built over time.
Perfectionism can easily become procrastination disguised as careful engineering. You usually won’t discover the best design by thinking indefinitely before writing code; trying a simpler approach often reveals information that was impossible to see beforehand. Build a small version, test it, and refactor when you have a concrete reason. Rewriting code is normal, and consistent effort matters more than finding an ingenious solution immediately.

I’m familiar with deadlines from school, but I usually felt bad about what I submitted. I suppose learning to separate “finished” from “perfect” comes with more experience.