Ingenuity is often misunderstood. It is not a matter of superior intelligence but of character. It demands more than anything a willingness to recognize failure, to not paper over the cracks, and to change. It arises from deliberate, even obsessive, reflection on failure and a constant searching for new solutions.
Failure is inevitable. It happens to everybody sooner or later. In fact, someone who has never failed at anything has either avoided pushing at the boundaries of their abilities or has learned to overlook their own mistakes.
Seek to identify the ways in which you tend to fail and try to resolve those that are worth fixing.
This is not about wallowing in self-pity about past mistakes nor is it an exercise in seeking perfection. Instead, the goal is to gain self-knowledge about the patterns, conditions, habits, and behaviors that lead you to failure. Armed with that self-knowledge, you can make conscious choices and temper the tendency toward idealism when applying Draw Your Own Map with an awareness of your boundaries and limitations.
By becoming conscious of the things that trip you up, you allow yourself the choice between working to fix these problems or cutting your losses. Accept that there will be some things that you’re not good at, or that would require a disproportionate investment of time and effort in order to make a small improvement.
This feeds into your accurate self-assessment, but it also enables you to set realistic limitations on your goals. You can’t excel at everything, and accepting these limitations is important, as it forces you to consciously acknowledge distractions and focus on your goals. It may mean accepting that you’re never going to make the time for that part-time PhD course, or it may mean letting go of old areas of expertise because you can’t devote the time to maintain those skills.
For example, Ade keeps a set of pages on his private wiki listing his current skillset and his limitations or boundaries. This enables him to select which boundaries to push outward (e.g., try maintaining a large codebase in a dynamically type-checked language) and where to stop wasting effort (e.g., accept that the 6502 assembler for the Commodore 64 is unlikely to undergo a massive resurgence).
In the programming language of your choice, use a simple text editor (later on you will see why it’s important that you don’t use an IDE for this exercise) to write an implementation of binary search in one sitting. Do not compile or run it yet. Now, write all the tests that you think you will need to verify that you have a correct implementation. Keep note of the bugs and problems you discover at this stage. Now, still without compiling or running the tests, go back and fix all the problems that you have discovered so far. Iterate until you’re satisfied that the code and the tests are both perfect. Finally, try to compile and run the tests. Most people will discover corner cases they hadn’t thought of and trivial little errors. Before you fix these errors, try to understand how they could have occurred in something you were sure was perfect. What does that tell you about yourself? Write down what you learn in the iterations between what you thought was perfect code and the point when you have code that actually compiles and passes all the tests. If you’re feeling particularly brave, get a friend to review the code and see what else she can discover.