I'm a software engineer specializing in great design of software -- every successful large software project ever made started out as a small software project that got larger. The key to a successful large project is knowing how to design software when it is small so it is capable of growing. Poor design in the early stages leads to high-entropy software that is difficult to maintain and add new features to years down the line. Good design in the initial stages allows new software features to be added easily. Good design doesn't take any more time than poor design, but you have to know how to do it.
Certain keys are very essential to good design. The beginning is the program's data structures, which form the foundation for any software project. The key to good data structure design is to make sure that the relationships between bits of data in your data structures are the same as the relationships between the objects or ideas that those data structures represent in the minds of your users. Any time these get out of sync, you are in for trouble -- but the trouble does not usually arrive immediately -- it can arrive months or years down the line. This delayed feedback cycle is one reason many software projects run late or fail. Any time the data structures are out of sync with the minds of users, there is the temptation to "patch" the problem by adding more data structures, that form a bridge between the existing data structures, and what you want to do. These "patches" are, unfortunately, "dirty hacks", that down the road will add complexity to your software. It is this complexity -- and more to the point, *unnecessary* complexity, that makes it more difficult to maintain or extend your software with new features in the future.
It is also extremely important to design the code structure correctly. It is very common to make basic errors like using global variables. Globals are very powerful, but should be used with care -- they connect separate components of the software with each other. (And be aware that many variables are global even when they are not called "global" in your particular programming language -- they can have other names). When you *want* something to apply "everywhere", globals are the right choice, because you change them in one place and the change is applied everywhere. But more often than not, globals are used when they shouldn't be, causing a change in one part of a program to cause another part of the program, that seems unrelated, to break.
Another minefield is object oriented programming. Objects are an extremely powerful and flexible programming metaphor -- and that's the problem. They are so flexible that they can mean almost anything, and they can make it easy for you to shoot yourself in the foot with excessive complexity. In reality, there is nothing wrong with non-object-oriented programming -- proper and thoughtful use of functions and libraries of functions -- so it is not necessary to use objects everywhere or make "everything" an object in your program. In particular, there is no advantage in doing "object-relational mapping" -- if you're doing this, it means you have designed all your data structures *twice* (once in the relational data model, and again in an object-oriented model), wasting effort. Furthermore, objects should only be used when they add *clarity* to a program, when they make it easier to understand how the program works, rather than more difficult. In certain situations, such as when polymorphism is needed to solve whatever problem your software needs to solve for the user, objects are a clear benefit, simplifying the design and adding clarity to the code. In many other situations, however, excess use of objects creates obfuscation, leading to maintainability problems and difficulty adding features to your software in the future.
And it is these complexity issues that impose limitations on how big your software can get, how many features it can have, and ultimately how well your business can grow and how well you can serve your customers.