There’s a brief but glorious period at the beginning of a new project – or a new company – during which a single team can perform all the necessary work. Communication is simple, coordination is straightforward, and it’s easy to keep everyone pulling in the same direction because the codebase and feature set are small enough that every member of the team can have a full, deep understanding of the whole shebang.
Inevitably, though, the project (assuming it succeeds) is going to grow in size and scope. You can scale for a while by throwing more people into the team, up to a point, but this only works for so long: past a certain size, the team can no longer form the tight working relationships that are necessary for effective software development, and unless the work is very rote and repetitive its managers are no longer able to keep up with a large number of engineers.
So sooner or later you’re going to be working with multiple teams.
Multi-team work introduces friction: someone needs to carry information back and forth between them and make sure they’re all working towards the same goal.
Strategies for Dividing Work
There are many ways to divvy up the work, each with its own advantages and disadvantages.
Technology-Based Teams
One straightforward-seeming method is to separate your teams by technology: you might put all your front-end developers on one team, all your back-end developers on another, maybe add a team of database specialists, and so on. This has some advantages: your engineers can hone their expertise on their single part of the tech stack, and it’s always immediately clear which work belongs to which team.
This strategy can work for a while for companies that have only slightly outgrown the single-team size, but at scale, the necessary cross-team coordination for every project becomes unwieldy: because everyone has to work with everyone else, you’re effectively working with a single big team with extra communication barriers built in.
The main disadvantage of this strategy is that virtually every project is going to involve collaboration between more than one team because pretty much every feature will include some front-end work, some back-end work, some database work.
Self-Sufficient Teams
Due to the reasons listed above, it’s generally accepted that it’s best to make teams as self-sufficient as possible: the more work a team can complete on its own, without needing to coordinate with others, the less friction there is in the development process.
But this brings its own complications: now it’s no longer clear-cut to determine which work goes to which team.
How to structure self-sufficient teams:
Feature Ownership
Should each team own a specific feature set within the application? This can lead to uneven or bursty workloads across teams, as not all features need the same amount of ongoing work or support.
Availability-Based Work
Should each team take on new projects based on availability? This evens out the workload but means engineers need to be familiar with the entire codebase and makes maintenance difficult as no one clearly “owns” any part of the code.
Services Model
Maybe a services model, where some teams focus on standardized features for other teams to consume? Not all features can be broken down this way, and even in cases where they can, the consuming team’s feature may have some specific requirements that the services team didn’t predict.
Strategies for Coordination
However the teams are structured, the strategies for coordinating work across multiple teams remain similar:
Communication
Communication, in all its forms, is critical. This can’t just be the leadership of each team talking to each other; the engineers need to have clear and defined ways to share information with each other. Make this as easy as possible for them, or they’ll end up duplicating work or building things in incompatible ways. Regular check-ins, whether a formal “scrum-of-scrums” structure or just throwing a recurring meeting on the calendar, are one form of this. But you should also look to have shared documentation and an obvious place for ad hoc communication to occur.
If each of your teams has their own private Slack channel, for example, consider making them public so people from team A can drop into team B’s channel to ask a question, or else establish a project-based channel which both teams’ members are subscribed to. Everyone should have access to everyone else’s observability tooling, so it’s not a mystery which APIs are a little janky or may need extra effort to make use of.
Clear Ownership
Make sure it’s obvious who owns what. You don’t want work falling into the cracks between teams, or two teams both thinking they’re responsible for the same tasks; this way lies conflict and duplication of effort. If the strategy you’ve used to define your teams’ areas of responsibility leaves a lot of fuzzy gray areas – work that could belong to multiple teams or that doesn’t clearly belong to any of them – consider either restructuring the teams or defining clearer boundaries between the teams that already exist.
Planning Ahead
Plan further ahead. A single-team project can operate on a sprint-by-sprint basis, defining the next bit of work as the current bit nears completion. Managing work across multiple team backlogs sometimes requires more pre-planning: if team A depends on specific output from team B, you’ll need to be able to predict team B’s schedule in order for team A to know it won’t be overloaded or left idle.
This doesn’t mean abandoning Agile and trying to preschedule every detail six months ahead. If anything, it requires more adherence to good Agile practices: if all your teams are doing a good job of sizing their tasks and have a predictable cadence, you’ll be much better able to plan cross-team dependencies with confidence that they’re in the right timeframe.
Standardization
Standardize what you can. Working with other teams’ code is a lot easier if everyone’s using the same tooling, the same profiler, the same build and deployment process, and the same coding conventions.
Adding and Removing Features
Add with confidence; remove with caution. If other teams are depending on an API or some other bit of functionality your team is responsible for, it’s almost always safe to add new capabilities. But removing or substantially changing how it works requires a lot more care to avoid breaking things downstream. Consider whether that changed functionality might be better off as its own separate module, or adding it as a new version while maintaining the existing functionality so downstream teams have the opportunity to adapt to the latest on their own schedule.
Hidden Costs of Shared Code and Sync Challenges
While shared code offers the potential for efficiency, it also introduces hidden costs that can affect productivity and delivery:
- Coordination overhead: teams spend significant time synchronizing changes and resolving conflicts, which can slow down overall progress.
- Technical debt: Shared codebases are prone to accumulating technical debt when changes are made without understanding downstream effects, leading to long-term maintenance burdens.
- Lack of standardization: without clear documentation or shared understanding, certain parts of the codebase may become reliant on specific individuals, increasing risk when those individuals are unavailable.
- Integration delays: dependencies between teams can cause bottlenecks, as one team’s work often depends on another’s availability or progress.
Reasons Why Pre-Production Observability Can Help Teams Sync and Reduce Costs
- Early issue detection provides insights into code behavior during development, allowing teams to identify performance bottlenecks or errors before they escalate.
- Improved Collaboration: shared tools ensure all teams access real-time data on APIs, dependencies, and system performance, reducing miscommunication
- Clearer ownership: highlights areas of the codebase needing attention, simplifying responsibilities, and minimizing gaps or overlaps in ownership.
- Continuous feedback loops: supports iterative development and improvement of workflows.
- Standardized practices: consistent tools across teams ensure that workflows remain unified and efficient.
- Cost reduction: reduces hidden costs like delays from unforeseen issues, rework due to miscommunication, and inefficiencies from duplicated efforts.
Conclusion: Shared Code and Sync Challenges
In this blog, Daniel Beck, a senior software developer and UX specialist, shared his journey and insights into the challenges of shared code and team sync. Having started in the early days of software development (yes, when NCSA Mosaic was the browser of choice). His journey offers invaluable lessons that teams can learn from and apply to their challenges.
Check out his blog at danielbeck.net/blog.