Time Management for Software Engineers

The nature of being a Software Engineer: you wake up every morning, and you have a million things you need to do, all of which will kill your startup/get your project cancelled if not done: a hundred tests to fix, a thousand messes to refactor, ten thousand features to add. This week, you have time to do two of them. Unless you decide to really push it, starting early and staying late, charging boldly into the abyss; then you might be able to start on a third.

The keys to being a successful Software Engineer are 1) accepting this reality, and 2) picking the right two. If you fail, you actually do get shut down, but if you succeed, you get to play again next week!

Here are the tactics, ideas, and mindsets I’ve found most helpful for dealing with this:

Focusing

A lot of the people who care most about time management really just want to avoid distractions and stay focused. I am, to some extent, one of those people, but the main thing I’ve learned is that website blockers and such work less well than getting good at noticing when you’re distracted and returning your focus to work right then.

Returning to work quickly is a skill with multiple components, and it takes practice. For example, if I catch myself reading something interesting at my desk, I note the article in a scratch document somewhere and go back to the task at hand. This habit was born of several insights:

  • It was a while before I stumbled on the idea of a scratch pad of interesting distractions. It really helps me “let go” of distractions emotionally, so I’m don’t feel like I’ll miss anything by going back to work right now.
  • The flashes of self-awareness you have while distracted are fleeting and you must take advantage of them. Seize the moment, before you get distracted again!

Some other tricks:

  • If some small tasks need to get done (e.g. sending a request, starting a test run), learn to use your desire desire not to do them as motivation to get them out of the way quickly. Tasks like that can inflate if given too much time, and by waiting until you don’t want to to do them, you’re sure not to give them much.
    • I’m a somewhat socially anxious person, and a practical consequence of that is that I spent way too long drafting emails before sending them. This came to a crisis point at my last job, such that I felt like I had no time for productive work due to email. I timed how long it took me to send an email, and it came out to almost an hour. I had enough time each day to send eight emails do nothing else. I realized I was in big trouble if that persisted, so I gave myself permission to send crappy email. I figured I’d send drafts and see what happened, and suddenly I was a lot more productive. The key is that it’s essential to keep small tasks small.
  • Social media works by exploiting FOMO, and to some extent that fear is grounded in reality: you really might miss something interesting if you stop checking social media completely. I’ve managed to convince myself, though, that anything I miss would come into my life in more than one way if it’s interesting enough. “Of all the most interesting things ever written, very few of them have been written on Facebook” (find citation)

Leave Buffer

The above applies in contexts other than staying focused as well. For example, I used to arrive late to meetings regularly, which I was embarrassed by but couldn’t seem to stop doing. On one occasion, I remembered that I had a meeting, checked the clock, and realized that if I stopped now I’d get to the meeting five to ten minutes too early. I noted that I’d need to leave soon. I was lucky enough, though, to be struck with a flash of insight right then: I was probably going to be late to the meeting because I’d set myself up such that the window of time in which I needed to remember to leave for this meeting was extraordinarily small. If I remembered too early, I’d just keep working. If I remembered too late, I’d arrive late. I started giving myself permission to arrive ten minutes early and sit on my hands, and leaving right when the thought to do so occurred to me, and suddenly I stopped being late nearly as often. (from doing this, I later learned that attempting to arrive early is a great way to arrive on time, in case the meeting moved or something).

Finally, at a previous company, some other engineers had made a little game where it would track the amount of time you spent working (by watching writes to disk) but turn it into a little game, where many writes close together if two writes occurred more than ~20 minutes apart,

Send Crappy Email

This advice pretty much lives in the title, but two experiences I had:

  1. I’ve always been a nervous communicator, I felt like

Don’t Sneak

Note: Should this maybe go with “Teamwork” instead? All this stuff seems related. Likewise, the whole “what would it take to get this done in T/2” tactic goes with both “Leave Buffer” and “Technical Risk”

Don’t try to sneak fun stuff in front of your project work without telling your manager/colleagues and then catch up later. You won’t actually be able to catch up, and your manager and you will be stressed. Be forthright about what you’re working on, but if you think something’s a good idea, try to convince your manager (or client, or whoever) to let you spend some percentage of your time on it (e.g. one day a week).

It’s Only OK

Sometimes you have a project in mind that’s so rad that you’re sure, if you can just scrape together a prototype, people will fall in love with it as soon as they see it. The result will justify the time you spent building the prototype, you’re certain. I can’t say honestly that this never works, but I can say that every engineer thinks this regularly, and that engineers should do this, on average, 99% less often than they want to.

Limiting Scope

This refers to two closely related tools: working incrementally (which is almost always a good idea) and cutting corners (which is usually a bad idea, but good engineers should know how to do it).

Working Incrementally

There’s a whole essay on this blog that explains why and how to work incrementally, but this paragraph refers specifically to slicing up your work1 and starting with the most critical slices. That incremental work essay offers more advice on on how to do so, but here I will simply assert here that a) every can be made as a series of small patches, and b) there major advantages to doing so.

The benefit of working incrementally with respect to time management is that if you get good at slicing up work, you get much better at dealing with deadlines too, because you can minimize the amount of pre-deadline work you have to do and shift most of the project into post-deadline revision. Stated in these general terms, this sounds like cutting corners (which is a tool in and of itself, as explained in the next section) but here I’m referring to, for example, adding a new API endpoint in time for a major release, and then adding features and options to it in subsequent minor releases.

Cutting Corners

Cutting corners—that is, writing bad code to save time—is a skill that good engineers should have (but should rarely use). There are a few ways to do it.

One big one is to prefer adding new code over changing existing code or factoring code out. Some examples:

  • Rather than change a method, copy it over to a new method so that you don’t have to update the existing callers or check that their use of the updated method is still correct.
  • Rather than adding a new method to an interface, create a new sub-interface with the method, so that you don’t have to update the existing implementations. Then pass around the new sub-interface only in the parts of the code that need the new functionality; unaffected code can keep using the old super-interface.
  • Copy and paste data around rather than designing new abstractions. For example, if many functions receive the same six arguments, they could probably be factored into their own class, but you can save time in the short term by not doing so; the receivers won’t need to be updated, and you won’t have to spend time designing and revising the abstraction.

The connection between cutting corners and working incrementally is that a) you can always construe cutting corners as incremental work by declaring that your project will proceed in two steps: 1) write messy, redundant code, and 2) fix it. You shouldn’t put your codebase into a messy state without a good reason, though. The longer it stays like that, the harder cleanup will be (e.g. new callers of your redundant code will be added and will need to be unified; you will slowly forget which functions were copied and why they were patched to differ from the originals; etc.).

Another way to cut corners that needs to be mentioned is to skip writing automated tests. Be very, very careful about doing this, though. On one particularly embarrassing project, I skipped integration tests to save time and ended up delaying the project by probably a month because of how slow we became at discovering and fixing the final issues in bug queue (we did end up building automated tests, which was how the project got finished, but only after wasting a lot of time first).

There are certain cases where skipping tests makes sense, though. One example: I once worked on a bug where our product would get slower and flakier over about ten hours, until it stopped working completely and needed to be restarted. After a lot of debugging, we determined that a previous refactor had accidentally moved a library call that started a TCP session to the inside of a retry loop, causing us to leak TCP sessions. Testing the fix (at least naively) would’ve required building a test harness in which we set up both our product and our upstream dependency and let them run for a day, but the fix itself was a two-line change: moving the function call back to the right place. Customers couldn’t use our product as it was, so we merged the fix with no test (until later).