Introduction: Why your enum can "leak" and how to avoid it
Imagine you're working on a large JavaScript project. You created an enum for order statuses: PENDING, SHIPPED, DELIVERED. Everything works perfectly until, in one of the modules, some developer (or yourself six months later) accidentally writes: OrderStatus.PENDING = 'CANCELLED'. Or, even worse, Object.assign(OrderStatus, { NEW_STATUS: 'hacked' }). Boom! The application logic breaks, the bug goes to production, and you spend a sleepless night debugging.
This is a classic problem that has gathered thousands of views on Stack Overflow. The question "How can I guarantee that my enums definition doesn't change in JavaScript?" plagues many. And for good reason: JavaScript is a dynamic language, and its objects are mutable by default. But there is a way out, and today we'll cover all the methods for reliably protecting your enumerations.
In this article, I'll show you several proven approaches: from simple Object.freeze() to advanced techniques with Symbol and TypeScript. You'll learn how to write immutable enums that can withstand any accidental (and not so accidental) changes.
The Problem: Why an enum in JavaScript is just an object
Unlike some languages (C#, Java, Rust), JavaScript doesn't have a native enum construct. Developers emulate them using plain objects:
const OrderStatus = { PENDING: 'pending', SHIPPED: 'shipped', DELIVERED: 'delivered'};This works, but this emulation has a fatal flaw: anyone can change the value. And it's not just malicious intent — it often happens accidentally:
- Accidental assignment in a loop or callback.
- Immutability? Forgot — and someone extended the object.
- Libraries that may mutate passed objects.
The problem is compounded by the fact that in large codebases, tracking such changes is difficult. That's why the question of protecting enums from changes is so popular.
Solution #1: Object.freeze() — your first shield
The simplest and most common way is to use Object.freeze(). This method makes an object immutable: you cannot add, delete, or modify properties.
How it works
const OrderStatus = Object.freeze({ PENDING: 'pending', SHIPPED: 'shipped', DELIVERED: 'delivered'});
// Attempt to change — won't workOrderStatus.PENDING = 'CANCELLED'; // Silent (in strict mode — error)OrderStatus.NEW = 'new'; // Won't be addeddelete OrderStatus.SHIPPED; // Won't be deleted
console.log(OrderStatus.PENDING); // 'pending' — value unchangedWhy is this useful? Object.freeze() works at the object level. It's a built-in mechanism that doesn't require additional libraries. It's perfect for protecting enums in JavaScript from accidental mutations.
Limitations of Object.freeze()
- Shallow freeze — if enum values are objects, they remain mutable.
- Doesn't protect against reassignment — if you use
letinstead ofconst, the variable can be overwritten entirely. - No errors in non-strict mode — changes are simply ignored, which can hide a bug.
But for simple enums, this is more than enough. If you need additional protection — read on.
Solution #2: Use const + Object.freeze() for complete lockdown
The combination of const (protects against variable reassignment) and Object.freeze() (protects against property changes) provides a solid defense. const ensures that the variable itself cannot be reassigned, while Object.freeze() makes the object's properties immutable.