Explain fan-in and fan-out, and the metrics afferent coupling (Ca), efferent coupling (Ce), instability (I = Ce / (Ca + Ce)) and abstractness (A). How do you use them to judge a design?
answer
- Afferent = Arriving (fan-in, Ca)
- Efferent = Exiting (fan-out, Ce)
- I = Ce / (Ca + Ce); 0 = stable/rigid
- Depend toward stability; stable ⇒ abstract
- A + I = 1 main sequence; pain = concrete + depended-on
basics
~20 sFan-in is how many modules depend on this one; fan-out is how many it depends on. Afferent coupling Ca counts incoming dependencies, efferent Ce counts outgoing. Instability I = Ce/(Ca+Ce) runs 0 (very depended-on, hard to change) to 1 (depends on everything, easy to change). Stable modules should be abstract.
solid answer
~60 s**Fan-in / afferent coupling (Ca)** = number of modules that depend on this module. **Fan-out / efferent coupling (Ce)** = number this module depends on. **Instability** `I = Ce / (Ca + Ce)` ranges 0..1: I=0 is maximally stable (many dependents, no dependencies — you cannot change it cheaply), I=1 is maximally unstable (depends on many, nothing depends on it — free to change). Martin's Stable Dependencies Principle says dependencies should point toward stability (decreasing I). **Abstractness** `A = abstract types / total types` in a package. The Stable Abstractions Principle says a stable package must be abstract, otherwise it is rigid: hence the target line `A + I = 1` (the *main sequence*), with distance `D = |A + I − 1|` measuring deviation. The two bad corners are the **zone of pain** (A=0, I=0: concrete and heavily depended-on — a shared utility library or schema everyone imports) and the **zone of uselessness** (A=1, I=1: abstractions nobody uses). In practice: high fan-in demands stability and a small, abstract, versioned surface; high fan-out signals a module doing too much or missing an abstraction; direction of dependency matters more than raw counts.
go deeper
Define fan-in as 'how many depend on me' and fan-out as 'how many I depend on', and note that high fan-out is usually a smell while high fan-in means reuse.
Add the Ca/Ce naming and the instability formula I = Ce/(Ca+Ce), explaining that stable means hard to change, and that dependencies should point toward stability.
Bring in abstractness, the main sequence A+I=1, distance D, and the zones of pain and uselessness, plus dependency inversion as the tool for flipping a bad arrow.
Discuss using these as trends and cycle/layer rules rather than hard gates, weighting edges by change frequency from version-control history, and the blind spots: runtime coupling, shared data stores, and dynamic wiring the static graph never sees.
## Fan-in and fan-out With a dependency graph whose nodes are modules and whose edges point from dependent to dependency: - **Fan-in** of module M = number of modules that *depend on* M (edges pointing in). Also called **afferent coupling, Ca** (afferent = carrying inward, as in afferent nerves). - **Fan-out** of M = number of modules M *depends on* (edges pointing out). Also called **efferent coupling, Ce**. The two are read very differently: | | High value means | Concern | |---|---|---| | **High fan-in (Ca)** | M is widely reused | M is hard to change; any breaking change ripples everywhere. Usually *good* — it means real reuse — provided M is stable and its interface narrow. | | **High fan-out (Ce)** | M pulls in many collaborators | M is fragile (any of them breaking breaks it), hard to test (many fakes), and often doing too much. Usually a *smell*. | So the healthy shape of a system is a small number of high-fan-in, low-fan-out foundational modules, a broad middle, and leaf modules (entry points, controllers, jobs) with high fan-out and no fan-in. Note that in the original structured-design literature fan-out was also a warning about a procedure calling too many subroutines directly, and high fan-in was praised as evidence of reuse — the same reading. ## Instability ``` I = Ce / (Ca + Ce) ``` - `I = 0` — nothing outgoing, everything incoming: **maximally stable**. "Stable" here does *not* mean bug-free; it means **hard to change**, because many things would have to change with it. It is also *independent* — it does not break when others change. - `I = 1` — depends on many, nothing depends on it: **maximally unstable**, free to change at will, but at the mercy of everything it uses. **Stable Dependencies Principle (SDP):** depend in the direction of stability — a module's I should be larger than the I of everything it depends on. A violation (a stable core depending on a volatile leaf) means the whole system is dragged along by a module that changes weekly. The classic remedy is **Dependency Inversion**: insert an interface owned by the stable side, and have the volatile side implement it, which flips the arrow. ## Abstractness and the main sequence ``` A = (number of abstract classes/interfaces in the package) / (total types) ``` **Stable Abstractions Principle (SAP):** a stable package should be abstract, so it can be extended without being modified (this is the package-scale version of the Open/Closed Principle). Combine SDP and SAP and you get a target relationship: ``` A + I ≈ 1 (the "main sequence") D = |A + I − 1| (normalised distance from it; 0 is ideal) ``` The two failure corners: - **Zone of pain** (A≈0, I≈0): concrete *and* heavily depended-on. Examples: a shared "common"/utility jar imported by every service; a database schema everyone couples to; a concrete framework base class extended everywhere. Any change is expensive and coordinated. Caveat: some things live here legitimately and are fine because they genuinely never change — the standard string library, or a stable value type. - **Zone of uselessness** (A≈1, I≈1): highly abstract and depended on by nobody — speculative interfaces, dead abstraction layers. Delete them. ## How to actually use this 1. **Look at directions before counts.** A cycle between packages (each is both afferent and efferent to the other) is worse than any single high number, and cycles violate the Acyclic Dependencies Principle. Break them with dependency inversion or by extracting the shared part. 2. **Treat high-Ca modules as published contracts.** Narrow surface, semantic versioning, deprecation windows, backward-compatible changes only. The cost of a breaking change scales with Ca. 3. **Treat high-Ce modules as suspects.** Ask whether the module has one job. A controller wiring ten services is fine; a domain rule needing ten collaborators is not — often it means a missing intermediate abstraction or a Facade. 4. **Use D as a trend, not a gate.** Tools (JDepend, NDepend, Structure101, ArchUnit-style checks, dependency-cruiser in JS) can compute these. Enforcing an absolute D threshold produces interface-for-its-own-sake noise; enforcing "no new cycles" and "layer X may not import layer Y" produces real value. ## Limitations and edge cases - **The metrics count edges, not risk.** Ten dependencies on stable standard-library types are cheaper than one dependency on a volatile internal module. Weight by volatility if you can (change frequency from version-control history is a good proxy). - **They are structural only.** They do not see runtime coupling (service A must be *up* for B to work), data coupling through a shared database, or coupling through reflection, dependency-injection strings, or message topics — all invisible to a static importer graph. - **Abstractness as a type ratio is crude.** A package full of one-implementation interfaces scores as abstract without providing any real substitutability. - **Granularity matters.** These are package/component metrics; applied per class they mostly measure size.
- Is high fan-in bad?No — it is evidence of genuine reuse. It is a *constraint*: the module must be stable, have a narrow well-documented interface, and change only in backward-compatible ways. High fan-in becomes a problem only when the module is also volatile and concrete (the zone of pain).
- A stable core package depends on a volatile leaf. How do you fix it without moving code?Apply Dependency Inversion: define the interface in the stable package (or a package it owns), have the volatile module implement it, and wire the implementation at runtime through a composition root. The source-code dependency now points from volatile toward stable, even though the call at runtime goes the other way.
- What do these metrics miss?Runtime/temporal coupling, shared-database coupling, coupling via reflection, DI strings or message topics, and — most importantly — the *volatility* of what you depend on. They count edges, not the probability or cost of the change each edge propagates.
Think of a road network. A motorway junction everyone uses has huge fan-in: closing it for repairs disrupts the whole country, so it must be built to a fixed, stable specification. A single house driveway has high fan-out relative to its importance — it depends on the whole road network, but nobody depends on it, so you can repave it any weekend.
saying these in an interview costs you the question
- Saying high fan-in is inherently bad — it is evidence of reuse, and only a problem when the module is also concrete and volatile.
- Reading 'stable' as 'bug-free' rather than 'hard to change because many depend on it'.
- Inverting the instability formula, or reporting instability as a raw count instead of a 0..1 ratio.
- Treating distance from the main sequence as a hard CI gate, which breeds one-implementation interfaces added purely to move a number.
- Assuming a clean static dependency graph means low coupling, ignoring shared databases, message topics and runtime availability coupling.