A fixed set of choices
Think of a traffic light. It’s red, yellow or green, never two at once, and never “purple”. An enum (short for enumeration) is a type with exactly that kind of fixed list of possible values.
Before enums, people used strings or numbers ("red", 2), and nothing stopped a typo like "gren" from compiling. With an enum, the compiler knows every possible value, so it can check two things for you: that you only use real values, and that a switch handles all of them.
An enum is a closed set of named values; a sealed hierarchySealed hierarchyA type whose list of subtypes is fixed and known to the compiler, so a switch over it can be checked for completeness.Open in glossary → is a closed set of types, each of which can carry its own data. Together they’re the sum types (tagged unions) of these languages: Swift enums with associated values, Kotlin sealed interface/sealed class, Java 17+ sealed interface with records, matched with Java 21 pattern matchingPattern matchingChecking a value's shape (its type, case or contents) and pulling data out of it in one step.Open in glossary → for switch, and TypeScript’s discriminated unions, narrowed by a literal kind field.
The payoff is exhaustiveness: a switch/when without a catch-all branch is checked by the compiler, so adding a case turns every place that must handle it into a compile error. The Detailed version covers when a closed set is the wrong choice, library evolution (@unknown default), and decoding values your app has never seen.
enum Light {
case red, yellow, green
}
func action(for light: Light) -> String {
switch light {
case .red: return "Stop"
case .yellow: return "Get ready"
case .green: return "Go"
}
}enum class Light { RED, YELLOW, GREEN }
fun action(light: Light): String = when (light) {
Light.RED -> "Stop"
Light.YELLOW -> "Get ready"
Light.GREEN -> "Go"
}enum Light { RED, YELLOW, GREEN }
static String action(Light light) {
return switch (light) {
case RED -> "Stop";
case YELLOW -> "Get ready";
case GREEN -> "Go";
};
}type Light = "red" | "yellow" | "green";
function action(light: Light): string {
switch (light) {
case "red": return "Stop";
case "yellow": return "Get ready";
case "green": return "Go";
}
}Notice there’s no default branch. Because the compiler knows all three values, it accepts the switch as complete. (In TypeScript, a union of string literals plays the role of the enum, and the check comes from the return type: if a value isn’t handled, the function could end without returning a string, and that’s an error.) If you later add case flashing, every switch without a flashing branch stops compiling, and the compiler gives you a to-do list of places to update.
Enums with values and behavior
Enums can also store a value per case and have their own properties and methods, which keeps related logic in one place:
enum CoffeeSize: String, CaseIterable {
case small, medium, large
var price: Double {
switch self {
case .small: return 2.0
case .medium: return 2.5
case .large: return 3.0
}
}
}
for size in CoffeeSize.allCases {
print(size.rawValue, size.price) // small 2.0, medium 2.5, large 3.0
}enum class CoffeeSize(val price: Double) {
SMALL(2.0), MEDIUM(2.5), LARGE(3.0);
val label: String get() = name.lowercase()
}
for (size in CoffeeSize.entries) {
println("${size.label} ${size.price}")
}enum CoffeeSize {
SMALL(2.0), MEDIUM(2.5), LARGE(3.0);
final double price;
CoffeeSize(double price) { this.price = price; }
}
for (CoffeeSize size : CoffeeSize.values()) {
System.out.println(size.name().toLowerCase() + " " + size.price);
}const coffeePrices = {
small: 2.0,
medium: 2.5,
large: 3.0,
} as const;
type CoffeeSize = keyof typeof coffeePrices; // "small" | "medium" | "large"
for (const [size, price] of Object.entries(coffeePrices)) {
console.log(size, price); // small 2, medium 2.5, large 3
}Every enum also knows its position or raw value, but be careful what you build on it:
Predict the output
What gets printed?
enum Size: Int {
case small = 1, medium, large
}
print(Size.large.rawValue)enum class Size { SMALL, MEDIUM, LARGE }
println(Size.LARGE.ordinal)enum Size { SMALL, MEDIUM, LARGE }
System.out.println(Size.LARGE.ordinal());enum Size { Small, Medium, Large }
console.log(Size.Large);With Int raw values, Swift numbers the cases from the first one you set: small = 1, so medium is 2 and large is 3.
ordinal is the position in the declaration, starting at 0: SMALL is 0, LARGE is 2. It changes if someone reorders or inserts a case, so don't save it or send it to a server.
ordinal() is the position in the declaration, starting at 0: SMALL is 0, LARGE is 2. It changes if someone reorders or inserts a case, so don't save it or send it to a server.
A numeric enum numbers its members from 0, like an ordinal: Large is 2. It changes if someone inserts a member, so don't store it. Prefer string literal unions (or a string enum).
| Swift | Kotlin | Java | TypeScript | |
|---|---|---|---|---|
| Simple enum | enum E { case a, b } | enum class E { A, B } | enum E { A, B } | type E = "a" | "b" (or enum) |
| Exhaustive match | switch (no default) | when (expression or statement) | switch expression, pattern switch (21+) | switch in a function with a return type, or a never check |
| All values | CaseIterable.allCases | entries | values() | an as const array the type is derived from |
Swift
Every switch must be exhaustive, statement or expression. Implicit Int raw values (case small = 1, medium, large) shift when a case is inserted: persist explicit raw values or names.
Kotlin
when over an enum or sealed subject must be exhaustive even as a statement (Kotlin 1.7+). Never persist ordinal: it shifts when cases are reordered.
Java
Only switch expressions and pattern switches are checked for exhaustiveness; a classic switch statement over an enum that misses a case compiles silently. Never persist ordinal().
TypeScript
Prefer string literal unions to enum: numeric enums are numbered like ordinals (and shift when members are inserted) and have a reverse mapping. A switch is checked only when the function must return a value, or when default assigns the value to never.
Check this part
Why is an enum safer than strings like "red" and "green"?Why is a union like
"red" | "yellow" | "green"safer than a plainstring?You add
case flashingtoLight. What happens to aswitchover it without adefault?You addFLASHINGtoLight. What happens to awhenover it without anelse?You addFLASHINGtoLight. What happens to aswitchexpression over it without adefault?You add"flashing"to theLightunion. What happens to aswitchin a function that must return a string?Which enum values are unsafe to store or send to a server?Which enum value is unsafe to store or send to a server?Which values are unsafe to store or send to a server?
A
switchused as a statement (not producing a value) misses a case of your enum or union. Does the compiler complain?