skip to content

Three teams own the rides, food and wallet features of one Flutter super-app; how would you decide the package boundaries, what goes in shared packages, and when not to split?

level: principalimportance: should knowfreq 24%

answer

  1. boundaries follow ownership, not layers
  2. thin core or coupling returns
  3. one resolution for the whole app
  4. the export list is a team contract
  5. split when seams have settled

basics

~20 s

Draw package boundaries along team ownership: one package per feature, a thin set of shared core packages, and an app shell. Split only when seams are stable and teams collide; otherwise keep feature-first folders in one package.

solid answer

~50 s

I start from ownership: each team gets a feature package it can change and test alone, and the app shell, owned by a platform role, composes them. Shared packages hold only what every feature truly needs, a design system, domain value types such as money, an API client and the contracts features call each other through, and each has a named owner, because anything in core recouples all three teams. Changes to a feature's exported library or to a core package get cross-team review; everything under `lib/src` is the owning team's business. The whole app still has one dependency resolution, so teams agree on one version of Flutter and of shared dependencies. I would not split while the product seams are still moving or the team is small: feature-first folders in one package, plus lints, give most of the benefit until merge conflicts, test time or ownership disputes show the boundary is real.

go deeper

for a junior

Recall that big Flutter apps can be split into feature packages plus shared packages, and that features should not import each other.

for a middle

Explain what belongs in a shared core package and why a single common package recouples features over time.

for a senior

Show how you enforce the boundaries: code ownership per package, lints in CI, per-package tests and review of each exported library.

for a principal

Own the judgement: name the evidence that justifies splitting, the costs you accept, and when you would keep one package or merge packages back.

## The decision in one line Package boundaries in a Flutter app are **ownership boundaries made enforceable**. Splitting is worth its overhead when several teams need to change, test and release parts of one app without stepping on each other; it is overhead without payoff when they do not. This answer assumes a super-app with **rides**, **food** and **wallet** features owned by three teams, shipped as one Flutter app. ## Where to draw the lines 1. **One package per team-owned feature.** Rides, food and wallet each become a local package. The unit is the feature a team owns end to end (screens, view models, repositories), not a technical layer. Layer packages (`data`, `domain`, `ui`) shared by all teams make every change a three-team change. 2. **An app shell.** The runnable app owns `main.dart`, the platform folders, the router, flavors and dependency wiring. It is small and changes rarely, and someone in a platform role owns it. 3. **A thin shared core**, split by purpose rather than one `common` package: - a design-system package (theme, shared widgets); - domain value types every feature uses (money, user identity); - an API client with auth and error handling; - a contracts package of interfaces features call each other through. The test for putting something in core: *do at least two features need it today, and will one owner maintain it?* If not, it stays in the feature. ## What each choice costs | Choice | Buys | Costs | |---|---|---| | Package per feature | Independent tests, enforced API, clear owners | More pubspecs, public-API upkeep, cross-package refactors | | Thin core split by purpose | Features depend only on what they use | More small packages to own | | One big `common` package | Easy to add to | Every team edits it; it recouples features | | Contracts package | No feature-to-feature edges | Interfaces to design and version | | Staying in one package | Zero overhead, easy refactors | Boundaries are only conventions | ## Rules that are not negotiable - **One resolution.** The app ships one version of each package; pub does not allow two versions of the same package in one app. Teams cannot pin different majors of an HTTP client or state library. Decide shared dependency upgrades together, and use one Flutter version for the whole repository, for example pinned with FVM. - **Acyclic graph.** Features depend on core, never on each other. - **Public surface is reviewed.** Each feature's top-level library, what it exports past `lib/src`, is the contract; widening it needs review from its consumers, while internals do not. ## Governance that keeps it working - Map folders to owners in your repository's code-ownership rules, so changes to `packages/wallet/` need a wallet reviewer and changes to core need the core owner. - Fail CI on `implementation_imports` and `depend_on_referenced_packages`, which catch reaching into another package's `src` and importing an undeclared package. - Run each package's tests on its own, so a feature that silently depends on another is caught. - Watch core's change rate: if most pull requests touch a core package, something feature-specific has leaked into it. ## When not to split Splitting is premature when: - one small team owns the whole app, so there is no ownership conflict to resolve; - product seams are still moving: moving a class between packages is a bigger change than moving it between folders; - the pain is build or reload speed: splitting into packages does not make the shipped Flutter build smaller or hot reload faster. In those cases, **feature-first folders in one package** give most of the locality, and the lines drawn in folders become package lines later with little rework. The signals to split are concrete: frequent merge conflicts between teams, test suites that one team cannot run without the others, disputes over who may change a file, or a feature that must be reused by a second app. ## A useful framing for the interview There is no single right graph. Show that you can name the unit of ownership, keep shared code small and owned, make the public surface explicit, and say what evidence would make you split or merge packages.

  • The wallet feature must also ship inside a second, standalone payments app. What changes?
    Wallet now has two consumers, so its exported library and its dependencies on core become a real versioned contract. It must not assume the super-app's shell, only the contracts it declares, and both shells must satisfy them. Keeping it in the same repository with local path dependencies avoids a publishing process while both apps evolve together.
  • Two teams want different major versions of the same HTTP client. How do you resolve it?
    They cannot both win: the app ships one resolution. Upgrade together, or hide the client behind the API-client core package so only that package's owner deals with the upgrade and features see a stable interface.
  • What would make you merge two feature packages back together?
    Constant changes that touch both, a contract that only exists to pass data between them, or both ending up with one owning team. A boundary that every change crosses is costing more than it protects.

saying these in an interview costs you the question

  • Splits by layer so every change spans all three teams
  • Puts everything shared into one ever-growing common package
  • Lets each team pin its own version of shared dependencies
  • Splits into packages to make hot reload or builds faster
  • Splits a small single-team app because big apps do