Thursday, October 22, 2026

Tactics vs Strategy

I guess I didn’t understand it when I was younger. Back then, doing things was just doing things. You decide you were going to do something, then you go about doing it. Simple.

But over the years, I was told otherwise. For instance, if you decide you are going to do something big that is relatively far out into the future, that is quite different than making an impulsive decision right now. E.g., deciding to go to University versus deciding to go out on the town tonight.

Two decisions, but very, very different.

When you set a goalpost for yourself far out into the future, that is basically a strategy. Since it’s so far away and often quite vague, it is nothing more than a direction you want to head. It’s essentially proactive; you’re looking trying to get ahead of the game.

When you make a decision right now, based on the immediate circumstances, that is tactical. It is reactive; you were forced into making that decision, and even if you choose not to decide, it is still a choice.

We see that in software development all of the time. Someone decided to build a system; that is the direction and the strategy. Someone else insists that a quirky feature get shoved in at the last minute; doing that work to appease them is tactical.

A development project without a direction is essentially a random walk. As people make suggestions about what to add to the codebase, if there is no way to evaluate them, then everything gets added. Eventually, the different random features conflict with each other. It is a mess. For example, you’re building a code editor, but someone wants it to play audio files. Why?

In that sense, having a direction carefully bounds the work, making it far easier to distinguish between good suggestions and bad ones. Someone once said something about how having a long-term plan allows you to make better short-term choices; that seems to be universally true.

Software development, however, is a little more complex. It is often a multidimensional tug of war, with lots of ropes and all sorts of people trying to constantly pull the work in only their direction. Which means that in order to not upset this balance, you frequently make tactical compromises.

For instance, there is some really good persistence technology that you know will work as expected, but for the initial release you bend to a request to dump in some trendy, rather flaky, tech instead. You know it won’t work as expected, but you lack enough control over the situation in order to stave off the make-work of using an inappropriate component.

What that implies is that many, if not most, of the tactical choices you end up making in order to keep everyone happy and keep the funding coming in are headed straight into technical debt. They are things that you must do now, but will also have to fix later, if the codebase makes it that far. If they are not fixed later, then it will hamper the lifespan of the code, cutting its utility short.

And this is the game in steering large development projects. You need to carefully set that strategy while getting bounced around by tactical issues. For those immediate problems, you either make good decisions in line with your strategy, or you make bad ones that you will remember to revisit and correct later. If you step back and see software development in that light, it gets a little easier to make better choices.

For instance, you’re tasked with building something significant, so you know where you are headed now. But the environment has lots of constraints and politics, so you know you need to weave around all of this. You don’t lose sight of that goal while you are forced to zigzag all over the place.

As the work progresses, you learn how to batch up some of the tech debt cleanup directly with new feature development. So, there was some older part of the system that you were forced to do poorly in the early days, but now, if you extend the scope a little on some new functionality, it will end up covering that mistake. From the earlier example, you put in some less-than-ideal persistence technology, but now you have upcoming work that needs a much larger schema. So, you wrap that new work along with switching the database technology as well. Kill two birds with one stone.

I used to describe this as alternating between short-term and long-term choices, but it is a lot more nuanced. You’re really keeping track of the bad tactical decisions and attempting to fix them along with newer but related work.

That works well when you do a non-destructive refactoring first, for instance, on some code before you extend it. The first work is pure cleanup, correcting earlier mistakes, while the second is moving forward with enhancements. But the second part is now way easier, because the pain was removed with the first effort. What should you refactor? The painful parts. Putting that into perspective, you did a strategic refactoring to undo poor tactical choices in order to smooth out some more tactical work.

Note that this does not only apply to the code. Basically, it is everything else too.

To get going quickly, you choose not to dive too deeply into understanding the domain for the project. That was a tactical choice. But if you add lots of domain knowledge to your resume, a lot more jobs open up later in your career, which is a strategic consequence. Technical knowledge is good, but lots of it goes stale quickly, whereas domain knowledge is more valuable in the long run. You can keep returning to it over the decades.

To expedite a project, people did an ultra-light job of dealing with the analysis. Again, a tactical choice. Later, though, the missing and incorrect information drives a lot of ‘unexpected’ changes, which delay the release, which was a more strategic goal. Fighting or arguing over the mistakes, another tactical choice, further pushes out the dates, making the project look strategically weak and more likely to get cancelled.

Or maybe there is demand to get the code out on a specific date. Again, very tactical. To make that window, lots of work gets cut, like carefully checking and editing the code, and creating a lot of tests. So, both the code quality and the QA fall short of what was actually needed, strategically. But the tactical reaction to the tsunami of operational bugs is to rush through fixing them. That burns everyone out, and the bugs just cascade or move around, since the quality of the fixes is no difference than the original coding; rushed. The strategic choice is to revisit that poor work, but raise the bar this time. Enhance the quality; do much better testing. The tactical twist is to head towards the buggiest code first, but strategically everything is suspect.

If you frame everything this way, it really helps. Without some strategy, you won’t go anywhere, just around in circles. So the goal is always to get yourself closer to the end, but the path there is often quite twisty. If you have to do something, you can decide whether it helps strategically, is useless, or will need to be revisited later. If you just make everyone happy, but get zero movement toward the goal, you lose. If you do too few strategic things, you lose. If it becomes a mess because of too many poor tactical choices, you lose. If you go nowhere because of too much make-work, you lose. You need to do as much of the stuff as you can in order to keep up the momentum and not be a mess.

It’s also worth noting that the goal should always be, at least, solving the user’s problems. If it’s just adding some feature, or hitting a release date, or playing with some technology, those are far too short-term to help. The goal has to be far enough away that it drives most of the tactical choices in a direction. It’s also worth noting that you probably won't be there if and when the project crosses the goal line, and that is fine. Your contribution was to head it off in a good direction; if it gets derailed long after you have left, that is unfortunate, but not your fault. The corollary to that is that if you join an existing project, then the most important thing to learn is the current strategy, and the worst tactical choice you can make is to ignore it or bend it way off into another direction. Strategy changes do happen, but they are slow-moving beasts, since they effectively reweight all of the previous tactical choices. You can’t start writing an accounting system, then switch to it being a video game, or vice versa. The first part is turned into make-work by the shift to the second goal.

In most cases, picking a strategy isn’t hard. But picking it and communicating it to everyone is a key part of leadership, which can be tricky. On the technical side, it is the dev lead's responsibility. If the team is headed one way and a programmer is going off in a different direction, their work now effectively has no strategy; it’s just random. So, it’s critical that everyone adopts the same strategy in order to work together. This means that if you are young or new to a team, even if you have ideas or preferences, you have to set those aside in order to effectively contribute. So, mostly, except for very early greenfield work, we don’t have to come up with a strategy; rather, we have to sort through the noise and figure out what it is. In many cases, it is actually ancient, but that is a good thing rather than a bad one.

No comments:

Post a Comment

Thanks for the Feedback!