This is page four of the online book "How to Become a Good Software Engineer: Advice from Programmers from Facebook, Amazon, Microsoft and more".

Table of Contents:


“In complex environments, “problem-solving” is a misnomer. Problem-solving is in reality problem trading” – Shreyas Doshi

[DS: As engineers we often build complex systems where solving a problem is actually not a solution but a choice: which problem do we choose to solve and which problems are created as a result. Being able to work with requirements that may be lacking in detail, carrying out research and ensuring you solve the right problem is another valuable skill.]

Properly defining a task is an important activity, but one which is not easy. Often tasks are defined only superficially when the programmer receives them. Thus before tackling the task, you need to understand the intent. Find out exactly who needs it and why. You will need to ask questions. In a third of cases, the task will change, and in another third, it disappears altogether.

Here is how Dmitry Fedorenko outlines how you should act:

- Hey Dmitry! Please, make a green button here.

- Not so fast! First, describe what problem you have. Why do you want a button? What will happen if we don’t do this?

This approach saves a great deal of energy. Often it turns out that you do not need a button, but a link, or text on a page, or you do not need to do anything. Better to spend your time on talking first, and then programming.

[DS: as a member of an engineering team you can and should contribute to a product’s decision-making process. You can offer a simpler solution, point out the lack of scalability of the proposed solution, or any other shortcomings in terms of technological implementation.]

“Lack of requirements is a constant problem that I face.” - Dmitry Fedorenko

Break down complex problems into small manageable chunks, the details of which can fit in your head, and which you can complete in four hours.

“Small libraries are more useful than one big one. An ideal library is a library that you can write in four hours and forget about.” - Vlad Popov

Tasks with a long list of business rules take hours to dive into every time you start working on them. Split large tasks into smaller independent parts to help it to load into working memory faster. Smaller tasks have smaller context and this makes context switching less of a problem.

In addition to decomposing large tasks to smaller ones, arrange your day so context switching happens as rarely as possible. Find tools to achieve this in the time-management section.

Try to use first principles and think of the best way to solve a problem apart from constraints. Consider tradeoffs between different solutions and have a bias towards simplicity.

Aim to assemble the smallest working prototype. Do not worry about quality or using the best tools like databases, queues, etc. Do it in a short timeframe to see how the feature works. This will allow you to get feedback on code from colleagues, or about a product from users, fast.

Look for the shortest path to a solution. Here is how a programmer from ok.ru explains it:

“When I was inexperienced, I wanted to do everything in the best possible way, and I would start out wanting to “build a bridge” thoroughly from one end to the other [on the first try]. But over time, I realized that such bridges end up in the wrong place [than where I would like them to be]. Therefore, with experience, I first “tread the path”, making at least a working prototype, then, when (and if) this stuff somehow works, I begin to put meat on the bones [adding more features, making it bulletproof, and improving the code].”

[DS:  when you have a good-enough solution, the deadlines are no longer pressing on you. Here you can make a decision to improve something, get feedback on the code, or save time for another task. In an attempt to make a sleek solution right away, I often ended up missing a deadline.]

Consider the requirements and resources that you have. Some tasks need immersion in the subject area, while others are a waste of time.

Often tasks that you do not know how to solve are obviously solvable. The solution for them comes from an intensive search on the internet. The downside of this approach is that it takes a lot of time — sometimes days or weeks. To be able to search faster, use Alex Khismatulin’s approach to form a broad domain knowledge by rapidly consuming a lot of high-quality articles (read about it in the “Self-development” section).

Another approach to solve the problem that you don’t know how to solve is to first make the simplest solution possible. Up to if `a = some_value then return expected_result`. You need to start somewhere, and once you’ve solved the simplest case, select the next requirement and repeat. Continue to iterate over requirements until you cover them all.

Tasks that you can’t solve now can be postponed for a couple of hours or until tomorrow. Fall asleep with a task in mind — often the idea comes in the morning.

[DS: This is due to the fact that our brain works in two modes, focused and diffused. Focused mode allows us to keep more complex details in our head. It’s more about depth of knowledge. Whereas the diffuse mode is a creative work mode using the full breadth of our knowledge. New ideas come in diffuse mode, in which the brain works in the background. This explains the belief that ideas come in the shower.]

Apply normative decision theory for problem solving.  A breakdown of the approach was shared by Vadim Tsesko:

  1. Collect all requirements and immerse into the domain
  2. Document thoroughly (creating a design doc) for subsequent retrospection of maintenance and development of the system
  3. Communicate with colleagues with relevant experience in order to collect an exhaustive set of alternatives
  4. Study existing solutions in the market, read relevant academic articles
  5. Analyze the advantages and disadvantages of each of the solution options, choice of solution
  6. Iterative design review through gathering opinions from more experienced colleagues

Whilst doing this, beware of the trap of becoming isolated and getting stuck.  Proactively collect opinions, be open and tolerant of criticism. Honestly weigh the pros and cons. In the end your goal is to make a decision in an evidenced-based manner.

Tips on solving a problem from Denis Bazhenov:

  • Conceptualize the task in terms of technical and business constraints, and under what conditions a solution will not work.
  • If the task is not demanding of resources, then develop a prototype through automated testing. It is easier to form a design and any flaws can be spotted earlier.
  • If the task is technically complex or related to distributed systems, it is important to analyze:
  • What can fail.
  • What steps need to be taken to make the system diagnosable.
  • What we will do if parts of the system start to fail.
  • How to eliminate failures and how to be on the safe side, for example collect special logs.
  • When everything fits together then it comes to the programming, and an understanding should be reached of what modules the system should be divided into, and how to organize work.
  • Consider making a prototype — this can be done even without coding.

Advice on how to implement a solution by Ruslan Torobaev:

  • Write unit tests if the task is large and it is not possible to keep all of its parts in your head.
  • Use specialized logs for integration code. It’s not possible to cover everything with tests in integrations.
  • For small components, test-driven development is useful. It helps to find flaws in the interface. If a component is hard to test, you immediately understand that you need to change the interface.
  • Application-wide analytics is useful for business, monitoring, and debugging.

It is important to be able to solve blockers that prevent you from completing a task. Here are some steps that you can take:

  1. Use an internal knowledge base or documentation, or external product documentation
  2. Get help from colleagues
  3. StackOverflow, Slack community channels, Discord, user groups, issues in a library or product repository, product channels.
  4. Look into the source code. For third-party products you can fetch a Docker image, start debugging with breakpoints and see how the product works from the inside. Do this step in parallel with the first three actions.

The last piece of advice is to train yourself to notice patterns. The faster you are able to identify them, the faster you will be at problem-solving.

“The problems are all different, but they all boil down to the same tasks” - a programmer from JetBrains

Considering that we cannot solve one problem without creating another, the best approach to problem-solving is treating it as an optimization problem with tricky boundary conditions. Therefore, concentrate efforts on choosing the best solution.

More to come…

“Art is never finished, only abandoned.” — Leonardo da Vinci

By the time I came to publish this article, I had even more software engineers on my list to talk to than when I started this project.

I will update this online book every time I have new valuable material.

Subscribe to my newsletter to find out when there will be more nuggets of wisdom and easy-to-forget simple rules added.