Dependencies between software development teams in large organizations are an almighty problem. Over the years I have seen many different strategies to cope with them — some successful, others more troublesome.
The most common problem with dealing with dependencies is that they are often ignored or simplified; sometimes silver-bullet, one-size-fits-all frameworks are applied erroneously: tackling dependencies always depends on context. This is why we need to look at dependencies holistically. If we’re to tackle what I like to call ‘evil dependencies’ — so named because of their capacity to disrupt and undo substantial work and effort — it’s vital that we bring together strategies and practices appropriately. In this post I’ll explain how that can be done.
The negative consequences of dependencies
First, though, let’s remind ourselves precisely why tackling dependencies is important: it can lead to increased time-to-market or possibly even project failure. The additional waiting times and increased project hand-overs that dependencies cause means it takes teams more time to develop features and bring them to customers.
Consider the context switching dependencies sometimes demand; this can negatively impact both individual and team productivity and performance.
To summarize, then, we can characterize dependencies as having a negative impact on these areas:
Lead time
Team performance
Motivation
Given the significance of these areas to effective software delivery, it’s worth thinking of dependencies as real and tangible risks. Indeed, this reframing can be helpful and change the way teams approach them — it can, I think, be the first step to properly tackling them.
Reframing dependencies as risks
Reframing dependencies as risks can help give dependencies the attention they deserve. It also guides you towards a more clearly defined structure for handling them — following a risk management process. In other words, managing dependencies should consist of:
Risk avoidance: Minimization of dependencies by eliminating them
Risk mitigation: Reduce their negative impact
Risk acceptance: Coordinating properly
Based on these three risk management activities, the following model has been created, to structure the practices that are necessary to master evil dependencies.
![Improvement cycle for fast flow](/content/dam/thoughtworks/images/photography/inline-image/insights/blog/blg_inline_Dirk_Lassig_Evil_Dependencies.png)
![Improvement cycle for fast flow](/content/dam/thoughtworks/images/photography/inline-image/insights/blog/blg_inline_Dirk_Lassig_Evil_Dependencies.png)
Improvement cycle for fast flow
The cycle starts with minimize, where we attempt to eliminate dependencies. This is done by realigning teams and team structure or even refactoring or modernizing software systems. In mitigate, the impact of dependencies will be reduced. This is supported by adopting new practices. In coordinate, the remaining dependencies will be dealt with. Throughout the rest of this post I’ll take a closer look at what each of these phases involves in practice.
Minimize dependencies
First, let’s look at how we can minimize dependencies. This might be the hardest to achieve, but is the most effective measure. It starts with uncovering them at their origins.
Identifying the origin of dependencies
Dependencies are typically caused by three things:
Requirements: Backlog items that are dependent on each other and cannot be implemented independently by different teams.
The engineering process: Sequential tasks along the engineering process (such as product design, coding and testing) distributed across multiple teams obviously can’t be performed independently.
Software components: Changing one part of the software system cannot be done without considering the effects this change could have on other components and other teams.
Eliminate dependencies
Once we’ve identified the source of dependencies, we can then take steps to eliminate them earlier on. The team composition and its area of focus are decisive levers for reducing dependencies. When done properly, the above mentioned origins have minimal influence on teams’ dependencies to each other.
Requirements: Backlog items should be more oriented towards goals and customer value. This is because they tend to be more independent compared to output-oriented backlog items, like tasks and component changes. One way of making a change is to organize teams around these backlog items like features or customer needs. The additional benefit of this is it should also lead to a stronger focus on the customer.
Engineering process: Create cross-functional teams that possess all necessary skills and competencies. Doing so will mean the team is better placed to handle everything end-to-end on their own; in short, they become less dependent on upstream or downstream tasks.
Software components: Focus teams on specific parts of the software system — like modules or microservices — that are loosely coupled to other parts of the system. This ensures the impact of a change has less impact on other teams. Domain-driven design is a useful approach when implementing this approach.
If you follow these recommendations you will get to what is described in the book Team Topologies as ‘stream-aligned teams’. As customer needs, business strategies and architecture evolve over time, this necessitates a realignment of teams to minimize dependencies.
It’s important to note that even with optimal stream-aligned teams, it’s impossible to completely eliminate all dependencies if the goal is to maximize customer value; they’re inevitable. The remainder of this article will discuss how we can manage these residual and persistent dependencies.
Mitigate the impact of dependencies
If we can mitigate the risks caused by dependencies, we can then reduce their (negative) impact. The extent of such an impact typically correlates to the amount of time and effort required to develop a given feature.. Consequently, if teams work together effectively to resolve a dependency — with fewer interruptions and less context switching — the negative impact should be much smaller and manageable. The following practices described below can help you better cope with dependencies.
Common standards
Let’s take the example that two teams want to agree on an interface quickly. If both teams haven’t met before and have different collaboration habits, use different technologies or have different opinions on how to define an interface, designing that interface will certainly take considerable time (and maybe some frustration too). Common standards can therefore be very helpful. Teams that work together should have a shared understanding about their architecture principles, engineering and deployment practices and common ways of working. Getting there requires a well-facilitated process.
Of course, one way of doing this is for standards to be prescribed in a top-down manner. However, this will undermine the team’s self-efficacy — and as a consequence, may hurt morale and motivation. A more effective approach is for shared standards to emerge from the teams using lightweight governance. Practices like open space technology, communities of practice or request for comments will help to build up this shared understanding in a way that fits the agile culture of teams. These practices facilitate collaboration beyond team boundaries. They support the exchange of knowledge and better enable the cross-team decision making needed to develop common standards.
Whole product focus
Another challenge for teams is to agree on a common schedule to resolve dependencies. Let’s take the previous example: so, the team wants to agree on an interface design and implement it. Both teams, then, need to schedule when to work on what. If teams prioritize in different ways, it could be disastrous; it may lead to long waiting times of weeks or even months.
To resolve this, both teams need to be aligned through a common understanding of the importance of a given task for the whole product. In other words, a whole product focus is imperative. This can be achieved by multiple practices, most notably having a single product backlog for all teams or aligning on a Lean Value Tree. Additionally, practices like overall retrospectives, common town halls and joint demos for running software involving multiple teams can help to foster an understanding of working together on a joint product.
Practices for unblocking dependencies
The Team Topologies approach also recommends adopting practices that change a blocking dependency into a non-blocking one. Instead of waiting for another team to finish a task, the team should have the capabilities to service their own needs. Growing the skills and platforms to support self-service should unblock more dependencies and ultimately improve flow. Engineering platforms are a great example of this.
Many engineering practices in recent years have emerged to make teams more independent, for example feature toggles, consumer-driven contracts, versioning APIs with depreciation strategies, event-driven communication or infrastructure as code. All these practices reduce coupling between components and enable greater asynchronicity. They also help move responsibility to the person or team that is best placed to solve a given problem, which means changes for a feature can be kept in one place. Teams should become fluent in these practices to reduce dependencies’ impact on flow.
Mentored code change
When a team needs to work together with another team on a feature, it’s usually because the other team needs to make changes to their code in order to realize the whole feature.
But what if the team could make the change in the code by themselves? For that, the team needs access to the code. More importantly, the team also needs to possess enough knowledge to navigate the code and make the necessary changes. High quality code is a prerequisite for this approach. Additionally, common standards can help here too, in the form of things like coding guidelines.
However, bear in mind that navigating unknown code is very challenging. You still depend on the other team’s knowledge to a certain extent. To tackle that, knowledge transfer should be organized — inter-team pairing is a particularly effective method.
Continuously minimize
Instead of learning to change the code of another team, a better approach may be to refactor and simply remove the dependency. If the two teams resolve a dependency and change the system’s design, in future they can work more independently. They need a good understanding about the general impact of dependencies on flow to see why this approach is necessary. Both must understand it’s better to improve the whole system than locally optimize — for this behavior to happen, teams need to work closely together.
Spread the knowledge
As mentioned in an earlier section, knowledge dependencies can hold up the development process. This is why it’s important to think carefully about how knowledge is transferred across teams.
For instance, imagine one team wants to implement a new feature but is dependent on another team which has done something similar before and has the relevant know-how. All too often teams begin by trying to acquire the knowledge they need without approaching the other team which is very inefficient.
Subsequently, it’s important to grow a culture of sharing to empower teams to go out and speak to other teams. Good practices for knowledge sharing can help resolve this type of dependency quickly and potentially reduce the time a team is blocked or struggling with a task.
Coordination practices
Even if we adopt practices to mitigate the impact of dependencies, it isn’t possible to completely eradicate them. Those that remain still need to be managed appropriately. This is where coordination comes in — coordinating on dependencies helps teams to align on what needs to be done. A whole product focus supports this alignment.
There are two different strategies:
The necessary work is scheduled sequentially. For example, one team starts and creates or changes their part of the code; when finished, the other team continues.
The teams work together at the same time. Both teams plan their work in the same iteration and collaborate over a period of time.
Usually, teams adopt the first strategy because it feels natural to them. It also means they can minimize interactions with members of the other team. In reality, though, due to late integration they will likely discover issues and, ultimately, need to interact with each other to fix them (even late in the process).
The second strategy, on the other hand, requires openness. Both teams discover new things together, feedback cycles are kept short and context switching is reduced, because the two teams are already working in the same context.
No matter which strategy is chosen, whether sequentially or synchronized, it requires teams to align their schedule. One practice is a very well known one. With big room planning multiple teams together plan upcoming iterations to achieve a common goal, like a release or program increment. It’s usually a long meeting. The SAFe framework has called this practice PI Planning.
However, this approach isn’t always the most effective. Fortunately, there are a couple of alternatives:
Rolling lookahead planning, in which teams look a couple of iterations ahead and forecast the upcoming scope — representatives from various teams then coordinate how this should be managed as a whole. A dependency matrix can help here.
For larger features, it’s recommended that multiple teams come together for close collaboration. This starts even earlier in the feature refinement process. The practices multi-team refinement and multi-team planning stem from the LeSS body of knowledge (Large Scale Scrum). Here, teams should have refinement meetings and planning meetings together. In the planning, each team derives their own iteration backlog. During the iteration the work of both teams is closely related and there is intense collaboration.
Conclusion
As we have seen, dependencies are risks. Elimination is the first step, but those that can’t be eliminated need to be mitigated and tackled through effective coordination. To this, product development organizations need to become fluent in strategies to minimize dependencies between teams.
We should recognize that organizations can never fully eliminate all dependencies. That’s why they must be ready to adapt their team topology and develop new ways to collaborate across teams especially if they want to drive towards greater customer-focus.
Good practices exist to help us. While some of them have been shared in this post, there’s no silver-bullet or one-size-fits-all framework that will do the job because every organization and every product is different. The key is to align the organization towards value and to grow an inter-team collaboration culture.