Morozov Blog

Contract-Based Development

Speeding Up Software Development at Scale: Lessons from Warner Bros Discovery

Introduction

At Warner Bros Discovery, the Direct-to-Consumer Ad Tech teams successfully adapted contract-first development principles to significantly speed up software delivery. Supporting major streaming services like HBO Max, Discovery+, and BT Sport - plus news and sports apps - these teams built monetization-focused systems across multiple platforms (BrightScript, C++, Kotlin, Swift, TypeScript) to deliver seamless ad experiences.

This article explores the specific approach that transformed how these teams work together - streamlining development through technical collaboration, quality-first principles, and smart dependency management. The techniques apply far beyond ad tech, so buckle up! 🚀

The Challenge: Complexity at Scale

Delivering global streaming services comes with its own unique set of challenges. Requirements for these services can vary depending on device, user, or even region, not even mentioning supporting multiple streaming services at the same time, like our teams do - HBO Max and Discovery+, for example. Add multiple device types to the mix, and things get really fun - features must work seamlessly across a wide array of platforms. On top of that, building features involves collaboration between several teams, each bringing something different to the table.

All this contributes to a highly intricate development environment. And it's not just about shipping features fast - it's about doing it at scale, across teams, while maintaining the highest standards for the user experience, performance and reliability. No pressure, right? 😅

The Solution: Contract-First Development

To tackle these challenges and speed up development, the teams adopted a "contract-first" approach. This works particularly well with POD (Product-Oriented Delivery) or Scrum team models where each participating team contributes a representative to the cross-functional pod. During POD meetings, technical leads from each team collaborate to define these contracts, with the feature owner driving alignment on requirements while architects ensure technical feasibility. The contract-first approach provides a blueprint that allows every team member to make progress simultaneously, effectively breaking down the dependencies that typically force sequential work. Let's unpack what this means:

1. Establishing Clear Contracts

The first step is defining the contracts for each component involved-essentially, the "rules of engagement." For example, if a new feature requires changes to both the front-end and back-end, the teams will define the API endpoints and data structures upfront. The front-end team can then work with mocked data while the back-end folks do their magic. This is a classic contract-first development approach.

However, this strategy works beyond just backend APIs! It can be used anywhere there's an interface or even a set of public methods - for example, between Model, View, and ViewModel layers in an app that's using MVVM architecture, or interfaces/abstractions used in any code following SOLID principles internal to a backend service, or even mapping layers between data streams transformations.

Consider two client-side teams building different layers of the same ad badge feature: Team A handles the data integration (fetching ad metadata and managing overlay timing) while Team B builds the overlay rendering system (positioning badges and handling display states). They define their interface contracts upfront-methods like getAdBadgeData() and showOverlay(position, content), plus events like adMetadataReady and overlayPositioned. Each team then mocks the other's implementation to develop and test independently, without waiting for the other team to finish their work.

Once all stakeholders agree on these contracts, teams can work independently, which means no one is waiting around for someone else to finish their part. 🙌

Where Contracts Live

These contracts don't need fancy tooling—they can live in TypeScript interfaces, OpenAPI specifications, shared documentation repos, or even well-commented code. The key is making them easily discoverable and keeping them updated. Many teams use their existing tools: Swagger docs for APIs, TypeScript definitions for client interfaces, or simple markdown files in shared repositories.

2. Breaking Dependencies with Mocks and Feature Flags

Mocks are our best friends here. 🐍 By simulating functionality that's not yet available, different teams can develop in parallel without being blocked by other teams. For example, the overlay team can mock getAdBadgeData() to return test ad metadata, while the data integration team can simulate the showOverlay() method to test their timing logic. This is a bit like a play rehearsal-you use stand-ins until the actual actors arrive.

Feature flags also play a crucial role. They let us safely roll out ad badge features in controlled phases-maybe starting with specific ad types only, then expanding to all sponsored content. And if things go sideways with the new badge positioning, we can easily turn them off. Nothing to see here, folks! 🚦

Bonus points for using assert functionality that's available in most languages nowadays. This is especially valuable in dynamically-typed languages (Python, JavaScript, BrightScript) where there's no compile-time type checking to catch contract violations. In statically-typed languages, the type system itself enforces many contracts, but asserts are still useful for runtime invariants that types can't express-like ensuring arrays aren't empty or values are within expected ranges.

assert documentation-particularly valuable for dynamic languages:

For static languages (still useful for runtime checks):

3. Parallel Development with a Focus on Quality

With contracts, feature flags and mocks in place, teams can work in parallel-a huge time-saver. The data integration team writes unit tests for ad metadata handling using mocked overlay methods, while the overlay team tests badge rendering with mocked ad data. Quality is maintained through comprehensive integration tests that validate the actual contract between teams-ensuring getAdBadgeData() returns the expected format and adMetadataReady events trigger proper overlay positioning. Feature flags help incrementally release ad badge features, minimizing risk. Think of it as dipping a toe in the pool before diving in-safer, smarter, and definitely less shocking! 🏊

Example: Displaying Ad Badges During Playback

Traditionally, adding a feature like displaying ad badges during ad playback involved a sequential workflow with multiple blocking dependencies. Backend team needs to finalize their API; AdSDK team needs to retrieve the data at the right time and shuffle it to the UI layer, then use Overlay system being actively developed by the Player team to render the overlay at the right time and in the right place. 🤦‍♂️

Enter contract-first development:

  • Backend Team: Defines the API contract for ad badge metadata-endpoints like /ad-metadata returning sponsor info, badge types, and positioning hints. They mock this API early so other teams can develop against it.
  • Player Team: Builds the overlay rendering system with contracts like showOverlay(position, content) and hideOverlay(). They work with mocked ad data to develop the overlay infrastructure independently.
  • Ad SDK Team: Sits at the intersection, responsible for badge positioning and display logic. They mock BOTH the backend API responses AND the player's overlay methods. This lets them develop the core positioning algorithms while both dependencies are being built simultaneously.
  • Testing and Integration: Each team validates their contracts independently-backend tests API responses, player tests overlay rendering, Ad SDK tests positioning logic with mocked data. Integration tests ensure the real backend data flows through Ad SDK positioning into proper player overlay display.

The beauty of this approach? All teams work concurrently on the same feature, dramatically shortening the implementation cycle. Win-win! 🏆

Drawbacks of Contract-First Approach

  1. Quality of Upfront Work: Since contracts are defined early, they need to be pretty accurate. Otherwise, re-working contracts deep into the implementation phase can be costly. It takes experience to get it right, but it's a worthy skill to hone. 🤷

  2. Communication: Effective communication is crucial. With multiple distributed teams, syncing up and adjusting to changes can be tricky and in the beginning, can feel not unlike trying to herd cats! 🐈

  3. Mock Management: All mocks need to be properly removed before shipping to production. It's easy to forget temporary mock implementations scattered throughout the codebase. A practical solution is establishing a consistent comment pattern like // MOCK: Remove before production or /* TODO_MOCK */ for all mock code. This way, any developer can simply grep the codebase for these markers and systematically clean up all temporary implementations, and CI/CD flows can flag mocks for production deploys. 🧹

  4. Contract Evolution: When requirements change mid-development, contract updates need to be communicated quickly to all teams. Version your contracts when possible and maintain backward compatibility to minimize disruption. This requires discipline but prevents cascading rework across teams. 📋

Benefits of the Contract-First Approach

  1. Reduced Bottlenecks: Defining contracts upfront means teams can work alongside each other rather than sequentially, dramatically shortening implementation cycles. While we didn't formally measure, our teams estimate this approach reduces development time by 20-30% for features requiring cross-team coordination. Nobody likes getting stuck in traffic! 🚗
  2. Improved Quality: Contracts and mocks enable early testing, catching issues before full integration.
  3. Faster Delivery: Parallel development means faster feature delivery-speed wins the day! ⚡
  4. Flexibility: Feature flags provide flexibility for incremental releases and easy rollbacks-like having an undo button for your features. 🔄

Conclusion

The contract-first approach has enabled Warner Bros Discovery’s Direct-to-Consumer Ad Tech teams to move fast while maintaining quality across multiple platforms. By defining contracts upfront, breaking dependencies with mocks, and managing feature rollouts with flags, teams can deliver high-quality features-quickly and efficiently.

Of course, it takes discipline, a lot of communication, and alignment-but once teams are on the same page, the benefits are huge.

Getting Started

If you're interested in trying this approach:

  • Start small: Pick one feature that involves 2-3 teams and use it as a pilot
  • Document contracts early: Use whatever tools you already have - TypeScript interfaces, OpenAPI specs, or even markdown files
  • Establish mock conventions: Agree on comment patterns like // MOCK: from day one to make cleanup easier

Have you tried a similar approach in your teams? Share your experiences (and war stories)! 💬


Sergey Morozov

Written by Sergey Morozov who lives and works in New York City building useful things.