Whatever thing you're working on, after you've come to understand how it all works, try to think about how to make it do the same things with a more compact representation, number of processes, or interface area.<p>Typically features have been incrementally added, most naturally by adding to the pre-existing design. It's rare that things get factored as thoroughly as possible. Doing so at every step is also inadvisable. The purpose of this exercise isn't to make the current system x% better, but rather to know what's being 'left on the table' that could be tidied-up in the future when a refactoring is due. With practice, you get good at noticing and revising designs in this manner while adding new capabilities paying refactoring costs incrementally.<p>What's even better is that the new smaller/simpler design is easier to explain, understand, document, and further adapt.<p>Another kind of exercise I do is think of all the steps/operations for the happy-path of an existing or new capability/feature in the context of where it fits in the most common use-case. List out the key operations/steps. See if there are any ways to reduce the number of steps or conditionals. This can often be achieved by making non-uniform things into uniform things. E.g. 'shapes' that can return their own bounding box, treating single or multiple things all as potentially multiple, etc--kind-of like programming with monads in a normal procedural language.<p>Simple doesn't come easy. Easy is doing the most obvious adjacent thing--usually the first thing that comes to mind. Simple is the last thing you can come up with because it's what's you get when there's nothing left to remove. i.e. "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." ― Antoine de Saint-Exupéry