A teammate has a bodied enum where each constant overrides apply(). They want to add a brand-new operation that depends on each constant. Compare extending the enum with a new abstract method vs handling it in an external when, and discuss the dispatch and maintainability tradeoffs.
answer
- new abstract fun = compiler forces all constants
- when expression (no else) = exhaustive & safe
- when statement + else = silent on new constants
- in-enum for intrinsic; external when for peripheral
- swelling enum methods = consider sealed/strategy
basics
~10 sAdding a new abstract method forces every constant to implement it (compile-safe, behavior stays with the constant). An external when keeps the enum closed but risks forgetting a constant unless the when is exhaustive.
solid answer
~50 sAdding a new `abstract fun` to the enum is the type-safe choice: the compiler forces every constant to override it, behavior stays co-located, and dispatch is virtual via the synthetic subclasses. The cost is touching the enum (and growing every constant body) — bad if the enum is a stable API others depend on, or the operation is a one-off concern that does not belong to the enum's responsibility. An **external `when (op) { ... }`** keeps the enum untouched and concentrates the new concern elsewhere, but you only get compile-time exhaustiveness if the `when` is used as an **expression** (or you opt into exhaustive statement checking); a `when` **statement** with an `else` silently swallows new constants. So: in-enum abstract method for core, intrinsic behavior; external exhaustive `when` (no `else`, expression form) for peripheral or layering-sensitive concerns.
code
kotlin · 15 linesenum class Operation { PLUS, TIMES }
// SAFE: expression, no else -> compiler demands all constants
fun symbolExpr(op: Operation) = when (op) {
Operation.PLUS -> "+"
Operation.TIMES -> "*"
}
// TRAP: statement with else -> a new constant silently hits else
fun symbolStmt(op: Operation): String {
when (op) {
Operation.PLUS -> return "+"
else -> return "?" // new constant -> '?' silently
}
}go deeper
Knows a new abstract method forces every constant to implement it.
Distinguishes when-expression exhaustiveness from a when-statement with else.
Weighs cohesion vs intrusion (shared contract, layering) and picks in-enum vs external when deliberately.
Defines team policy: intrinsic behavior in-enum, peripheral concerns in exhaustive expression-form when, and thresholds for migrating to sealed/strategy.
## Two ways to add per-constant behavior later ### Option A — new `abstract fun` in the enum ```kotlin enum class Operation { PLUS { override fun apply(a:Int,b:Int)=a+b; override fun symbol()="+" }, TIMES { override fun apply(a:Int,b:Int)=a*b; override fun symbol()="*" }; abstract fun apply(a: Int, b: Int): Int abstract fun symbol(): String // newly added } ``` - **Compile-safe**: every constant *must* implement `symbol()`; forget one and it won't compile. - **Co-located**: each constant's `symbol` sits next to its `apply`. - **Virtual dispatch**: `op.symbol()` resolves on the constant's synthetic subclass. - **Cost**: you edit the enum and every constant body grows. If the enum is a published/shared type, this is intrusive; if the concern (say, a UI label) is not the enum's job, it pollutes the domain type. ### Option B — external `when` ```kotlin fun symbolOf(op: Operation): String = when (op) { // expression form Operation.PLUS -> "+" Operation.TIMES -> "*" } ``` - **Enum untouched**: good when the enum is stable/shared or the concern belongs to another layer. - **Exhaustiveness depends on form**: as an **expression** (assigned/returned), the compiler requires all constants and rejects a missing one (no `else` needed). As a bare **statement** with an `else`, adding a new constant compiles silently and routes to `else` — a maintenance trap. - **No virtual dispatch**: it's a centralized branch; behavior is *not* with the constant. ## Dispatch nuance Option A uses polymorphism over the closed set of synthetic subclasses — adding a constant automatically demands the override. Option B is structural branching — safety hinges on writing the `when` as an exhaustive expression **without** `else`. Many bugs come from a `when` statement with `else` that silently mishandles a newly added constant. ## Choosing - **Intrinsic, core behavior of the type** -> in-enum abstract method (compile-forced, cohesive). - **Peripheral concern, or the enum is a shared/stable contract, or it belongs to another module/layer** -> external exhaustive `when` (expression, no `else`). - **Many such concerns** -> consider extracting to a sealed hierarchy or a strategy map; a swelling enum with ten abstract methods is a smell. ## Keywords `abstract fun`, `override`, `when` expression vs statement, exhaustiveness, `else`, synthetic subclass dispatch.
- Why does adding `else` to a `when` over an enum undermine future safety?With `else`, the compiler no longer flags a newly added constant; it routes to `else`, so the new case is silently mishandled instead of failing the build.
- When is editing the enum the wrong call even though it's compile-safe?When the enum is a shared/published contract or the new concern belongs to another layer; then you bloat the domain type and force every consumer to recompile.
saying these in an interview costs you the question
- Adding an else to an enum when and calling it exhaustive
- Assuming a when statement gives the same exhaustiveness as an expression
- Always editing the enum regardless of ownership/layering
- Believing external when uses virtual dispatch
- Letting an enum accumulate many abstract methods without considering a refactor