Switch statement
enumerations
programming best practices
default case
software development

Switch statement without default when dealing with enumerations

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Omitting default in a switch over an enum can be a good design choice when you want missing cases to stay visible. A default branch can hide the fact that a new enum member was added and never handled. Whether leaving out default is safe depends on the language, the compiler checks available, and whether enum values can come from outside your control.

Why Developers Omit default

The main reason is exhaustiveness. If every enum member should have explicit behavior, a default case can make the code look complete even when it is not. Without a default, many compilers, linters, or IDEs can warn when a new enum member is introduced but not handled.

Java switch expressions are a good example:

java
1enum Status {
2    PENDING,
3    PAID,
4    CANCELLED
5}
6
7static String label(Status status) {
8    return switch (status) {
9        case PENDING -> "Pending";
10        case PAID -> "Paid";
11        case CANCELLED -> "Cancelled";
12    };
13}

If another enum value is added later, this switch expression becomes incomplete and the compiler forces you to update it.

When This Is a Good Practice

Omitting default is a strong choice when:

  • the enum is closed and controlled by your codebase
  • every value deserves explicit logic
  • the language can warn or fail on non-exhaustive handling

This is common in business logic where each state transition or display label should be reviewed deliberately.

In C#, a switch expression provides a similar benefit:

csharp
1public enum Status
2{
3    Pending,
4    Paid,
5    Cancelled
6}
7
8public static string Label(Status status) =>
9    status switch
10    {
11        Status.Pending => "Pending",
12        Status.Paid => "Paid",
13        Status.Cancelled => "Cancelled"
14    };

If you later add another enum value, the compiler points to the now-incomplete switch expression.

Why a default Can Hide Bugs

Consider a default branch that returns "Unknown" for anything unrecognized. That may sound safe, but it can silently swallow newly added enum members and produce wrong behavior in production.

java
1static String label(Status status) {
2    switch (status) {
3        case PENDING:
4            return "Pending";
5        case PAID:
6            return "Paid";
7        case CANCELLED:
8            return "Cancelled";
9        default:
10            return "Unknown";
11    }
12}

If REFUNDED is added later, the code still compiles and now mislabels a real business state as unknown. That is exactly the kind of bug exhaustive handling is meant to prevent.

When a default Still Makes Sense

There are valid reasons to keep a fallback branch:

  • the enum comes from another library and may gain new values before your code is updated
  • invalid numeric enum values can enter through deserialization or database corruption
  • the runtime environment is safety-critical and must degrade gracefully instead of throwing

In those cases, the fallback should still be noisy. Logging, metrics, or an exception is often better than silently returning a harmless-looking value.

Defensive Pattern: Throw Explicitly

If you want exhaustive intent and still need a runtime guard, a good compromise is to avoid a normal default behavior and throw instead.

csharp
1public static string Label(Status status) =>
2    status switch
3    {
4        Status.Pending => "Pending",
5        Status.Paid => "Paid",
6        Status.Cancelled => "Cancelled",
7        _ => throw new ArgumentOutOfRangeException(nameof(status), status, "Unhandled status")
8    };

This still surfaces unexpected values immediately. It is different from a permissive default because it fails loudly instead of hiding the problem.

Match the Pattern to the Risk

For internal enums controlled by the same repository, exhaustive handling without a permissive default is usually the better option. For external or serialized enums, some form of defensive fallback may be justified. The important thing is to avoid a branch that makes unknown values look normal.

This is less about syntax and more about change detection. Good enum switches make future maintenance mistakes obvious.

Common Pitfalls

  • Adding a default case that silently hides newly introduced enum members.
  • Assuming all languages provide equally strong compile-time exhaustiveness checks.
  • Forgetting that invalid enum values can still appear through casting or deserialization.
  • Returning a generic fallback string where the correct behavior should be an exception or explicit review.
  • Using statement-style switches when a switch expression would provide stronger completeness checks.

Summary

  • Omitting default can be the right choice when every enum member should be handled explicitly.
  • Exhaustive switch expressions help the compiler catch missing cases after enum changes.
  • A permissive default branch can hide real bugs.
  • If unexpected values are possible, prefer a loud fallback such as an exception or logging.
  • Choose the pattern based on whether the enum is closed and fully controlled by your code.

Course illustration
Course illustration

All Rights Reserved.