I start with what I am trying to achieve and list the different design approaches I can think of, adding advantages and disadvantages of each one as they come to mind. By the time I’ve written down all my thoughts on a design decision, it is often clear to me which approach I favor.
This can be repeated for more and more detailed aspects of the implementation (e.g. “which function should this be added to” or “what to name this function/struct/variable”) until I feel like I can come up with the remaining details as I’m writing the code. If I get stuck somewhere later on, I can always go back and add more details in the text document.
For larger or more important features, this list can be cleaned up and become documentation or perhaps a comment somewhere, but I often find that the writing is a useful tool to get unstuck and to clarify my thoughts even if I end up never reading it again.
First thing I figure out is how I want to interact with a thing. Whether that's a program or a class or a function. How do I want to call it? What parameters do I want to pass? What do I want it to return? How do I want to use what it returns? So, basically, write the interface first.
If I'm building something with multiple parts rather than a single class or function, I'll map out how these things all work together. A loose graph of interactions. Invoke A and have it return X; invoke B and pass X to it and it returns Y; etc.
Then I'll consider failure modes and think about what should happen if something doesn't work out quite right. Is it possible to route failures to a centralized cluster of error handlers so I don't have to implement error handling at every level?
Finally, I'll think about whether I can map behaviors to defined data structures instead of controlling flow with if/else patterns.
Once I have all that written down or mapped out, then I'll start implementing from the outside in. Stub the object, methods and return dummy data structures that fit until I have a complete system that's interacting the way I want. Then I go in and implement the actual functionality I need.
The last part--implementing functionality--often implies modifications to my initial thought process. But it's easier to understand what those changes affect if you've already mapped your design. So you might think B can produce Y with I parameter. Maybe it turns out you can't. So now you need to add a new param. Where is that going to come from? Well, you don't have to invent that out of nowhere because you've already mapped out what is happening. You know that you need to either add another node to the call graph or change the return value somewhere else.
By the end of the process, you have a working program, and you've also done a lot of your documentation work as well.
Again, I have no idea if this is what anyone else is talking about here. But this is how I personally work. It annoys the hell out of some people. But it works for me and helps me create sane software with interfaces people can remember over time.