Working backward

After becoming frustrated with overengineering, I adopted an approach to prevent it: setting a goal and working backward — beginning with an end in mind.

Luís Soares
CodeX
6 min readFeb 25, 2022

--

Working backward involves setting goals and asking what’s needed to achieve them. It consists of working from the end to the beginning, from knowns to unknowns.

“I can’t see a way through,” said the boy. “Can you see your next step?” “Yes.” “Just take that,” said the horse.” The Boy, the Mole, the Fox and the Horse

From knowns to unknowns

We begin with the end in mind; we define the goal and keep it in sight. We think about what the system should do (the intent/purpose/outcome) and then work backward to achieve it. We start with what; we then worry about how. We work our way from the former to the latter. We work backward, from knowns to unknowns, from what’s desired to how to achieve it. Distinguishing them ensures our mental power is fully applied to each. It brings clarity to thoughts. It ensures alignment on a goal before jumping hands-on. We could call it purpose-driven (or intent-driven) development.

We always take care to separate the problem and solution. The solution doesn’t matter if the problem isn’t worth solving. Shape Up

⬇️ start here

assert ⇢ act/arrange
UI/comments/test/steps ⇢ implementation
end (goal) of function ⇢ beginning of function
goals/idea ⇢ execution/strategy
what/problem ⇢ how/solution
knowns ⇢ unknowns

Begin with the goal

When you set out to solve a problem, you imagine a world where that problem is solved. Then you ask, “What do I need to make this a reality?”. You start with what’s known — people don’t have the problem anymore — and navigate to the unknown — how to get there. This is true whether you’re creating a new product or building capabilities into an existing one. For example, when writing proper user stories, state only what problem you are trying to solve and only worry about how later (when kicking off the story). Starting with the user problem (user story) ensures you always solve real problems rather than developer whims or company assumptions.

Beginning with the end in mind (the goal) is the most helpful way to focus on the real problem and prevent discussing solutions too early. It also helps to do solely what’s needed and avoid divertions. The opposite of this is developing technological solutions and forcing them into user needs.

You may prepare an architecture, a strategy, or a plan to reach a goal, but that doesn’t mean you need them written in stone. It’s crucial to remain flexible and adapt to new information as you move forward. Your understanding of the domain keeps evolving as you deliver small chunks of value. Often, you only understand the problem when trying to solve it. Meanwhile, the world keeps changing.

Purpose is what makes work energizing and engaging. People need more than a list of tasks. Lean Software Development

Coding backward

Working backward applied to coding has plenty of examples. As a developer, I often begin with the following:

  • Function steps. List the steps needed to achieve the function’s goal without caring how to implement them, treating them as calls to non-existent functions. This creates a roadmap for implementation and ensures a top-down approach.
  • Comments. Write a plan as comments in plain language before writing code, clarifying intentions, and improving system design.
  • Commit message: Prepare a commit message without actually committing. This is a helpful way to focus while ensuring that each git commit is a small, valuable, and meaningful step.
  • Function goal. Start writing a function by determining its return or side effect, then work your way up, asking what’s needed at each step.
  • Assertion. Start with the assertion in a test case, then move upwards to implement the necessary code.
  • Test. In Test-driven development (TDD), you write a test (with a single assertion) that specifies the desired behavior and then implements it.
  • Interface. Start work on a user story by designing the top-level interface (e.g., UI, API), then iteratively implement lower-level components based on needs.

A guiding light

If you have a dream or a life goal, you set one and picture yourself in it. You devise ways to achieve it, keep the goal in mind as a beacon of light, follow a strategy, and keep reassessing whether you succeeded.

Polaris makes an excellent fixed point from which to draw measurements for celestial navigation.

Having no defined goal can be tiring, confusing, and demotivating. For example:

  • We often observe early solutionizing, like designing UIs upfront before any research. Some people go as far as starting with database schemas. We must think about the goal before jumping too fast to write code. We spend too much time discussing technical details, but we should first agree on what is being solved. If you see someone jumping too fast on a solution, ask: “What problem are you trying to solve?
  • Autonomous teams should be trusted to fulfill a high-level goal. How they get there is their concern; otherwise, you’re micromanaging.
  • Most documentation should be written reactively, not proactively. When a problem arises, it proves its necessity and relevance; it’s time to document its solution in a runbook.
  • Architecture work shouldn’t be done upfront, but some decisions may need to be made using a risk-driven approach. You imagine what could happen (and the impact) and work backward to see what could prevent/mitigate it.
  • Many meetings would be shorter and more productive if people started by setting up the desired outcomes.

Defining the goal and keeping it in sight would solve all that. Besides, assigning purpose to what we do is one of the pillars of motivation. Keeping the goal in sight provides focus and direction. It commits your efforts to a goal. You can always answer why you’re doing something.

There is a great deal of evidence that people are hardwired to care about purposes. […] There is also much evidence that people suffer when they lack purpose. Intrinsic Motivation at Work

Keep it lean

One of the lean development tenets is to maximize value while minimizing waste. I hypothesize that the end-in-sight mindset is a critical weapon against waste. It keeps overengineering in check because, for everything you do, you can ask if it’s in line with the set goal. If you ask, “Why am I doing this?” you can trace it back to the root cause. Just ask why a few times and check if you were led to something with user value (I do it when I’m feeling lost). If you don’t know the goal, ask why or don’t do it. To put it bluntly, don’t do anything without an end in sight.

To get something done, you have to start with some notion of what is wanted — the goal that is to be achieved. Then, you have to move yourself or manipulate someone or something. Finally, you check to see that your goal was made. The Design of Everyday Things

This approach will forcefully discard everything that looks like “Let’s do it for the future”, “We gotta try this new tech”, “We need a generic solution” or any other diversion from the set goal (regardless of its size). Keeping the destination clear ensures you never forget why you’re doing something. The golden rule is that you can only pick the actions that lead you there with less effort and discard the rest. It’s easier to focus and cut short diversions from the goal. This applies to everything we do, from product strategy to writing functions.

Question first and model second. That is, know what questions you want the model to answer before you build it. That way you will have an easier time choosing its abstraction level and what details it includes. Just Enough Software Architecture

Learn more

--

--

Luís Soares
CodeX

I write about automated testing, Lean, TDD, CI/CD, trunk-based dev., user-centric dev, domain-centric arch, ...