The Biggest Enemy of Focus and Flow: Interruptions

Bill Gates once said: “Most people overestimate what they can do in one year and underestimate what they can do in ten years.”

I like to paraphrase it as “Most developers overestimate what they can do in 10 minutes and underestimate what they can do in 1 hour”.

Interruptions are a productivity killer. You can do barely anything in 6 blocks of 10 minutes but do a lot during an uninterrupted 1-hour focus session.

Even minor interruptions can be costly. For instance, when debugging intricate bugs, you need to build and hold a complex state in your head. A simple question from a colleague can destroy it and force you to restart your debugging session from scratch.

Here are the most common sources of interruptions with ideas on how to deal with them

Notifications

Nowadays, every application wants to grab our attention. Just the basics, like email, text messages, or work chat, send many notifications per hour. It’s impossible to stay focused and respond to these notifications. The truth is that most, if not all, of them can wait.

Turn off most notifications on your phone and laptop if you can. For the remaining ones, disable the sound entirely, or at least for the duration of your focus session. Consider leaving your phone in a different room and putting your laptop in the focus mode.

Check your email or work chat between focus sessions. Attend to messages that need to be answered and decide what to do with the remaining ones – likely, almost none will require action.

Teammates

If you work in the office, your teammates are a blessing but also a curse. While spontaneous brainstorming sessions, ad hoc design discussions, and the ease of receiving help to solve on-call issues are gold, they are also a source of constant distraction.

My first strategy to avoid these distractions is to… hide. I found a nice area in my building a few floors away from my team, which I can use to prevent interruptions. I often go there if I need to finish something important urgently. Occasionally, I work from home to get work done, which I view as an ultimate form of hiding.

Another option is to use noise-cancelling headphones. I don’t even play the music – I use the headphones to isolate myself and signal to other team members that I don’t want to be interrupted. It is not as effective as hiding, but it works fine most of the time.

Internet

It is hard to tell what is more difficult: coding with or without the Internet. On the one hand, a significant part of coding is searching the Internet for solutions to more or less trivial problems. On the other hand, it is so easy to fall into the downward spiral of checking “just one more thing.”

My way to combat these temptations is to use a dedicated virtual desktop for my coding session. The only programs open on this desktop are my developer tools, such as IDE, terminal, or a simulator, and a dedicated browser window, where I am not allowed to open any website unrelated to what I am working on. (I gave up on a multi-monitor setup long ago when I realized I used the other monitor(s) for things whose only purpose was to distract me, like email). Some people also use programs like Cold Turkey to block the Internet and avoid distractions.

Ultra focus mode

Long plane trips can trigger the ultra-focus. They definitely do it for me. Because the environment is restricted, there are few chances for interruptions. Ever since I experienced it for the first time, I have been trying to recreate it on the ground. If you have yet to discover it, take the opportunity on your next long flight.

Computer Science fundamentals are still important

I feel uncomfortable admitting that when I got my job at Microsoft in 2005, I didn’t know how to implement BFS (Breadth First Search). At that time, I was six years into my professional software engineering career and held a master’s degree in Computer Science.

I still didn’t know this a few years later when, one day, during lunch, someone mentioned that the candidate they had interviewed earlier “didn’t even know how to find the shortest path in a graph.” This made me feel horrible – I knew I couldn’t do it, too. In a few seconds, I turned from a seemingly successful Software Engineer at Microsoft into an impostor. This incident prompted me to improve my Computer Science fundamentals.

While I eventually learned BFS, my example shows that making solid progress in the software engineering career does not require knowing Introduction to Algorithms (a.k.a. CLRS) by heart. This is even more true today than it was twenty years ago. These days, we are working at a much higher level of abstraction. Most common algorithms, like binary search, are included in standard libraries, and implementing them is a waste of time.

Despite this, I urge every developer to learn the basics of Computer Science.

Why learn Computer Science fundamentals?

The answer is simple: learning Computer Science fundamentals can boost your career. Here is how.

You will understand unfamiliar systems quickly.

Once you learn the basic algorithms and data structures, you will see them everywhere. You will realize that HTML and XML documents are trees, key-value stores can be conceptually thought of as HashTables, and that from a single consumer perspective, Kafka topics are queues. This is powerful as it allows you to understand the behaviors and limitations of these systems even if you don’t know them deeply.

When I first started using git, I felt overwhelmed. I couldn’t understand how it worked, and the commands didn’t make much sense. One day, I watched yet another git explainer on YouTube, and it mentioned that git is a DAG (Directed Acyclic Graph). Overnight, I became a git guru fixing team members’ repos.

You will be able to solve challenging problems.

Most of the problems software developers deal with day-to-day don’t require advanced computer science knowledge. Once in a while, however, a challenging problem pops up. This is when knowing algorithms and data structures can be very handy. I remember struggling for a couple of days on a dependency graph problem when my co-worker pointed out that I could solve it quickly if I applied topological sorting.

You will do better at coding interviews.

Many interviews, especially in Big Tech companies, include coding questions. Usually, these problems can be solved with one of the standard algorithms. If you are familiar with them, you have a better chance to do well during these interviews.

How to keep skills up to date.

Most skills degrade over time. Algorithmic skills are not different. Even if you remember the idea behind an algorithm or a data structure, the details can get hazy with time. This is why it is good to refresh your skills periodically. There are many ways to do it. My favorite is participating in Advent of Code. Advent of Code is an online event in December where you are presented with two problems every day until Christmas. Solving these problems is a lot of fun and allows me to brush up my programming skills. But the best part is Solution megathreads – dedicated subreddits where others post their solutions. I check these threads once I solve the problems for the given day. They are a trove of startling insights, unconventional approaches, and programming tricks I would never think of, and I learn a lot from them.

Image: https://cs.stackexchange.com/a/107190

RFC Pull Requests: Because Code Wins Arguments

I believe in submitting clean and complete pull requests (PRs). I like PRs to compile without errors or warnings, include clear descriptions, and have good test coverage. However, there is one category of PRs where these standards do not apply – RFC (Request For Comments) PRs.

What are RFC PRs?

RFC PRs are PRs whose sole purpose is to help reach alignment and unblock development work.

When to send RFC PRs?

In my experience, sending RFC PRs can be particularly helpful in these situations:

  • When working in an unfamiliar codebase.
  • When trying to determine the best implementation approach, especially when there are several viable choices.
  • To clarify ideas that are easier to explain with code than with words

The first two scenarios are like asking: ‘This is what I am thinking. Is this going in the right direction? Any reasons why it wouldn’t work?’

The third one is often a result of a design discussion or a PR review. It is like saying: ‘I propose we approach it this way.’

The quality of RFC PRs

RFC PRs are usually created quickly to ask questions or demonstrate a point. These PRs are not expected to be approved, merged, or thoroughly reviewed, so there is little value in doing any work that does not directly contribute to achieving the goal. Adding test coverage is unnecessary, and the code does not even need to compile. For example, it is OK not to update most call sites after adding a function parameter.

I advise against trying to merge RFC PRs. Doing this rarely ends well. First, it is hard to change the reviewers’ perception after they saw the first quick and dirty iteration. Furthermore, comments from the initial attempt may mislead reviewers and cause unnecessary iterations. It is often easier to submit a new PR, even if an earlier RFC PR heavily inspires it.

Storytime

I used an RFC PR recently while researching how to integrate our services with a system owned by another team. The integration could be done in one of two ways: using a native client or an API call. The native client offered limited capabilities, but understanding the consequences of these limitations was difficult. I decided to send an RFC PR to get feedback on my approach and quickly learned that the client approach wouldn’t work and the reasons why.

Simple prioritization framework for software developers

Prioritization is an essential skill for software developers, yet many find it challenging to decide what task to focus on next. In this post, I would like to share a simple prioritization method I have successfully used for years. It is particularly effective for simple tasks but can also be helpful in more complex scenarios.

Here is how it works

To determine what to work on, I look at my or my team’s tasks and ask: Can we ship without this? This question can be answered in one of the three ways:

  • No
  • Maybe (or It depends)
  • Yes

First, I look closely at tasks in the No bucket. I want to ensure they all are absolutely required. Sometimes, I find in this bucket tasks that are considered a must by the task owner but barely meet the Maybe bar from the product perspective. For example, it is hard to offer an online store without an inventory, but search functionality might not be needed for a niche store with only a handful of items.

Then, I look at Maybe tasks. Their initial priority is lower than the ones in the first bucket, but they usually require investigation to understand the trade-offs better. Once researched, they often can be immediately moved to one of the remaining buckets. If not, they should go to the Yes bucket until they become a necessity. For the online store example, a review system may be optional for stores selling specialized merchandise.

Tasks in the Yes bucket are typically nice-to-haves. From my experience, they usually increase code complexity significantly but add only minimal value. I prefer to skip these tasks and only reconsider them based on the feedback. An example would be building support for photos or videos for online store reviews.

The word ship should not be taken literally. Sometimes, it is about shipping a product or a feature, but it could also mean completing a sprint or finishing writing a design doc.

This framework works exceptionally well for work that needs to be finished on a tight timeline or for proof of concepts. In both cases, you need to prioritize ruthlessly to avoid spending time on activities that do not contribute directly to the goal. But it is also helpful for non-urgent work as it makes it easy to identify areas that should get attention first quickly.

Storytime

One example where I successfully used this framework was a project we worked on last year. We needed to build a streaming service that had to aggregate data before processing. We found that the streaming infra we used offered aggregation, but it was a new feature that had not been widely adopted. Moreover, the aggregation seemed very basic, and our estimations indicated it may not be able to handle our traffic volume.

After digging more, we learned that the built-in aggregation could be customized. A custom implementation seemed like a great idea because it would allow us to create a highly optimized aggregation logic tailored to our scenario.

This is when I asked: Can we ship without this?

At that point, we did not even have a working service. We only had a bunch of hypotheses we could not verify, and building the custom solution would take a few weeks.

From this perspective, a custom, performant implementation was not a must-have. Ensuring that the feature we were trying to use met our functional requirements was more important than its scalability. The built-in aggregation was perfect for this and did not require additional work.

We got the service working the same day. When we started testing it, we found that the dumb aggregation satisfied all our functional requirements. Surprisingly, it could also easily handle our scale, thanks to the built-in caching. One question saved us weeks of work, helped avoid introducing unneeded complexity, and instantly allowed us to verify our hypotheses.

Accelerate your software engineer career by learning to love ‘boring’ technologies

Software developers are constantly bombarded with new, shiny stuff. Every day, there is a new gadget, JavaScript framework, or tool promising to solve all our problems. We want to use them all and cringe when we think about the boring technologies we use in our day-to-day jobs.

But we should love these boring technologies.

They get the job done. They pay the bills. They are there for us when we need them the most.

Most boring technologies have been around for years or even decades. They are versatile and battle-tested. Are they perfect? Absolutely not! They all have quirks and problems, but there are good reasons why they survived most of the contenders trying to replace them.

I don’t suggest that you avoid new technologies altogether. On the contrary, I encourage everyone to explore what’s new out there. You just need to know when to do this and understand the risks.

I recommend using mature technologies for risky, critical, or time-sensitive projects. When there is little margin for error, you want to use a reliable technology you understand and can work efficiently with. New technologies rarely meet these criteria.

Smaller or non-critical projects are perfect for trying something new and learning along the way, as long as you understand the consequences if things don’t work out. The most common issues with new, often unproven, technologies include:

  • trade-offs – you are trading a set of reasonably well-understood problems for a set of unknown problems
  • bugs or unsupported scenarios whose fixing is outside of your control
  • issues with no acceptable workarounds may block you or even force you to pivot to a different technology
  • poor documentation and support; limited online resources
  • the technology may unexpectedly lose support forcing you to either sunset your product or rewrite it completely

Occasionally, you will get lucky, and the new technology you bet on will become a ‘boring’ technology. I experienced this at my first job, where we decided to experiment with the .NET Framework. At that time, it was still in the Beta stage. Today, millions of developers around the world use the .NET Framework daily. I ended up working with it for more than 15 years. I even contributed to it after I joined Microsoft, where I worked on one of the .NET Framework teams.

I had less luck with my Swift SignalR client. For this project I needed WebSockets, but at that time, there was no support for a WebSocket API in the Apple ecosystem. I decided to use the SocketRocket library from Facebook to fill this gap. When my attempts to get help with a few issues failed, I realized that the SocketRocket library was no longer maintained. This lack of support forced me to look for an alternative. I soon found SwiftWebSocket, which I liked because it was small (just one file), popular, and still supported by the author. Moving to SwiftWebSocket required some effort but was successful. Fast forward a few years, and the library stopped compiling after I updated my XCode. I fixed the issues and sent a pull request to make my fixes available to everyone, but my PR didn’t receive attention. I also noticed that more users complained about the same issues I hit but were not getting any response. This unresponsiveness was a sign that the support for this library ended also (the author later archived the project). As I didn’t want to go through yet another rewrite, I forked the code, fixed compilation issues, and included this version in my project. Eventually, Apple added native support for WebSockets to the Foundation framework. Even though it was a lot of work, I was happy to migrate to this implementation because I was confident it would be the last time!