skip to content

What is EnumSet, and why should you prefer it over HashSet when the elements are enum constants?

level: middleimportance: should knowfreq 62%

answer

  1. bit vector keyed by ordinal -> one long for <=64 constants
  2. factory methods only: noneOf/allOf/of/range/complementOf/copyOf
  3. iterates in declaration order
  4. no nulls, not thread-safe, one enum type
  5. modern type-safe replacement for int bit flags

basics

~20 s

EnumSet is a Set built specifically for enum values. Internally it stores membership as bits, so it is much faster and uses far less memory than a HashSet of the same enums. Create one with factory methods like EnumSet.of(A, B) or EnumSet.allOf(MyEnum.class).

solid answer

~40 s

EnumSet is a specialized Set implementation for a single enum type. Internally it represents the set as a bit vector — typically a single long when the enum has up to 64 constants — where each bit marks whether a constant is present. That makes add/remove/contains very fast bitwise operations and keeps memory tiny compared to HashSet, which hashes and boxes objects into buckets. You never call its constructor; you use factory methods: EnumSet.noneOf(E.class), EnumSet.allOf(E.class), EnumSet.of(...), EnumSet.range(LOW, HIGH), and EnumSet.complementOf(other). Iteration order is the enum's natural (declaration) order. It is not thread-safe and rejects null elements. It is the idiomatic, efficient choice for flag-like sets of enum values, replacing old int bit-field tricks while staying type-safe.

code

java · 9 lines
java
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

EnumSet<Day> workdays = EnumSet.range(Day.MON, Day.FRI);
EnumSet<Day> weekend  = EnumSet.complementOf(workdays); // SAT, SUN

boolean isWeekend = weekend.contains(Day.SAT); // true, a bit test
for (Day d : workdays) {
    // iterates MON, TUE, WED, THU, FRI in declaration order
}

go deeper

for a junior

Knows EnumSet is a Set for enum values created via factory methods like EnumSet.of(...).

for a middle

Explains the bit-vector backing (one long for <=64 constants), the memory/speed win over HashSet, and uses range/complementOf/allOf appropriately.

for a senior

Discusses RegularEnumSet vs JumboEnumSet, declaration-order iteration, null/thread-safety constraints, and frames EnumSet as the type-safe replacement for int bit fields.

for a principal

Guides API design to expose EnumSet for flag-style options, weighs immutability/defensive copies at boundaries, and reasons about when the constant-factor wins actually matter for the system.

## Background: Set and enum A **Set** is a collection with no duplicate elements (`java.util.Set`). A common implementation is **HashSet**, which stores elements in a hash table: it computes each element's `hashCode`, places it in a bucket, and uses `equals` to detect duplicates. An **enum** is a type with a fixed list of named constants; each constant has an **ordinal** — its zero-based position in the declaration order (`MONDAY` = 0, `TUESDAY` = 1, ...). ## What EnumSet is `EnumSet<E>` is a `Set` implementation in `java.util` designed to hold **only constants of one enum type**. It is abstract; you obtain instances through static **factory methods**, never `new`. Its key idea: since an enum has a small, fixed set of constants each with a known ordinal, membership can be stored as a **bit vector** — one bit per constant, where bit *i* is 1 if the constant with ordinal *i* is in the set. For enums with **64 or fewer** constants, the JDK uses `RegularEnumSet`, backed by a single `long` (64 bits). For larger enums it uses `JumboEnumSet`, backed by a `long[]`. Either way, the storage is a handful of machine words. ## Why it beats HashSet for enums - **Speed:** `add`, `remove`, and `contains` become bit operations (set/clear/test a bit), which are O(1) with tiny constants and no hashing, no boxing, no bucket traversal. Bulk operations like `addAll`/`retainAll` become single bitwise OR/AND. - **Memory:** a `HashSet` allocates a backing `HashMap`, an array of buckets, and a `Node` per element. An `EnumSet` is essentially one `long` (or a short `long[]`). For a set of enum constants this is dramatically smaller. - **Predictable iteration:** EnumSet iterates in the enum's **natural order** (declaration order), not the effectively-random order of HashSet. - **Type safety:** it can hold only the one enum type, caught at compile time and runtime. ## Creating one (factory methods) ```java enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } EnumSet<Day> empty = EnumSet.noneOf(Day.class); // empty EnumSet<Day> all = EnumSet.allOf(Day.class); // every constant EnumSet<Day> some = EnumSet.of(Day.MON, Day.FRI); EnumSet<Day> work = EnumSet.range(Day.MON, Day.FRI); // MON..FRI inclusive EnumSet<Day> weekend = EnumSet.complementOf(work); // SAT, SUN EnumSet<Day> copy = EnumSet.copyOf(some); ``` `range` uses ordinal order; the first argument must not come after the second. `complementOf` returns the constants **not** in the given set. ## Constraints to remember - **No null elements:** adding `null` throws `NullPointerException`. - **Not thread-safe:** wrap with `Collections.synchronizedSet(...)` or use external synchronization if shared across threads. - **One enum type per set:** mixing types is impossible by design. - It still implements the full `Set` contract, so it drops into any code expecting a `Set`. ## Why not just use int bit fields? Before enums, programmers used `int` constants ORed together (`STYLE_BOLD | STYLE_ITALIC`). That is fast but type-unsafe (any int compiles), hard to print, and error-prone. EnumSet gives you the same compact bitwise performance **with** type safety, readable `toString`, and the full collection API — so it is the modern replacement for int flag fields.

  • Why does EnumSet have no public constructor?
    It is abstract with two private implementations (RegularEnumSet for <=64 constants, JumboEnumSet for more). The static factory methods choose the right one based on the enum's size, hiding that decision.
  • Is EnumSet thread-safe, and how would you make it safe for concurrent use?
    No. Wrap it with Collections.synchronizedSet, or guard access with your own lock; there is no concurrent EnumSet variant.

Like a row of light switches, one per enum constant: flipping switches (bits) is instant and the whole panel is just one small board, versus HashSet keeping a labeled folder for every item.

saying these in an interview costs you the question

  • Calling new EnumSet<>() — there is no public constructor.
  • Adding null to an EnumSet (throws NPE).
  • Assuming HashSet iteration order equals EnumSet's; EnumSet is declaration order.
  • Treating EnumSet as thread-safe in shared mutable use.

context