Question
In JavaScript, how can I define a set of enum-like values so that the definition does not change later?
For example:
my.namespace.ColorEnum = {
RED: 0,
GREEN: 1,
BLUE: 2
};
// later
if (currentColor == my.namespace.ColorEnum.RED) {
// whatever
}
Is this enough to give the object the important characteristics of an enum in JavaScript, or is there a better way to define it so the values cannot be modified accidentally?
Short Answer
By the end of this page, you will understand how JavaScript handles enum-like patterns, why plain objects are not truly protected by default, and how to make them safer with Object.freeze(). You will also learn common enum patterns, how developers use them in real projects, and what mistakes to avoid when comparing or mutating enum-like values.
Concept
JavaScript does not have traditional enums in older standard syntax the way some languages like Java or C# do. Instead, developers often create enum-like objects: plain objects that group a fixed set of named values.
A basic example is:
const Color = {
RED: 0,
GREEN: 1,
BLUE: 2
};
This works well for readability because Color.RED is clearer than writing 0 directly in your code.
However, a plain object is still mutable by default. That means someone could do this later:
Color.RED = 99;
Color.YELLOW = 3;
That is the main problem: the object behaves like a container of constants, but JavaScript does not automatically protect it.
To make an enum-like object safer, developers usually use Object.freeze():
const Color = Object.freeze({
RED: 0,
GREEN: 1,
BLUE: 2
});
Object.freeze() prevents:
- adding new properties
- removing properties
- changing existing properties
This makes the object much closer to the idea of an enum.
Why this matters:
- It prevents accidental bugs.
- It makes shared constants predictable.
- It improves readability across a codebase.
- It helps teams standardize values like states, roles, status codes, or categories.
In modern JavaScript and TypeScript, there are additional patterns, but for plain JavaScript, a frozen object is one of the most common and practical solutions.
Mental Model
Think of an enum-like object as a printed legend on a map.
RED,GREEN, andBLUEare the labels.0,1, and2are the official meanings.
If the legend can be edited at any time, the map becomes unreliable. Someone could change RED from 0 to 99, and now everyone reading the map may interpret it differently.
Using Object.freeze() is like laminating that legend so nobody can rewrite it by accident.
So the mental model is:
- a plain object = a writable label sheet
- a frozen object = a fixed set of official labels
Syntax and Examples
The most common enum-like syntax in JavaScript is a frozen object.
const Color = Object.freeze({
RED: 0,
GREEN: 1,
BLUE: 2
});
let currentColor = Color.RED;
if (currentColor === Color.RED) {
console.log("The color is red");
}
Why this is better
constprevents the variableColorfrom being reassigned.Object.freeze()prevents the properties inside the object from being changed.===is preferred over==for exact comparison.
String-based enums
In many JavaScript projects, strings are easier to debug than numbers:
const Color = Object.freeze({
RED: "RED",
GREEN: "GREEN",
BLUE: "BLUE"
});
let currentColor = Color.GREEN;
if (currentColor === Color.GREEN) {
console.log("The color is green");
}
This can be easier to read in logs, APIs, and JSON data.
Namespaced version
If you are using a namespace-like object:
const my = {
namespace: {}
};
my.namespace.ColorEnum = Object.freeze({
RED: 0,
GREEN: 1,
BLUE: 2
});
This preserves your original style but makes the enum-like object immutable.
Step by Step Execution
Consider this example:
const Status = Object.freeze({
PENDING: 0,
APPROVED: 1,
REJECTED: 2
});
let currentStatus = Status.APPROVED;
if (currentStatus === Status.APPROVED) {
console.log("Approved request");
}
Here is what happens step by step:
Object.freeze({...})creates an object with three properties.- The object is frozen, so its properties cannot be changed, deleted, or extended.
currentStatusis assigned the value ofStatus.APPROVED, which is1.- The
ifstatement comparescurrentStatuswithStatus.APPROVEDusing===. - Since both values are exactly
1, the condition is true. - The message
Approved requestis printed.
Now imagine someone tries this:
Status.APPROVED = 99;
console.log(Status.APPROVED);
In non-strict mode, the change fails silently and the value stays 1.
In strict mode, JavaScript throws an error.
That protection is the key benefit of freezing the object.
Real World Use Cases
Enum-like objects are common in real applications whenever you have a small fixed set of allowed values.
Application states
const RequestState = Object.freeze({
IDLE: "IDLE",
LOADING: "LOADING",
SUCCESS: "SUCCESS",
ERROR: "ERROR"
});
Used in UI code to track loading status.
User roles
const UserRole = Object.freeze({
ADMIN: "ADMIN",
EDITOR: "EDITOR",
VIEWER: "VIEWER"
});
Used in permission checks.
API result types
const ResultType = Object.freeze({
JSON: "json",
TEXT: "text",
BLOB: "blob"
});
Used to decide how to parse a response.
Order or payment status
const PaymentStatus = Object.freeze({
PENDING: "pending",
PAID: "paid",
FAILED: "failed"
});
Used in e-commerce systems and backend logic.
Logging levels
const LogLevel = Object.freeze({
INFO: "info",
WARN: "warn",
ERROR: "error"
});
Used to standardize logs across services and scripts.
Real Codebase Usage
In real codebases, enum-like objects are usually not just stored and compared. They are used as part of broader patterns.
Centralized constants
Teams often keep enums in one file so all modules use the same values:
export const OrderStatus = Object.freeze({
PENDING: "PENDING",
SHIPPED: "SHIPPED",
DELIVERED: "DELIVERED"
});
This avoids duplicated string literals across the codebase.
Guard clauses and validation
Developers often validate input against enum values:
const Role = Object.freeze({
ADMIN: "ADMIN",
USER: "USER"
});
function isValidRole(role) {
return Object.values(Role).includes(role);
}
function createUser(name, role) {
if (!isValidRole(role)) {
throw new Error("Invalid role");
}
return { name, role };
}
Switch statements
Enums are often used with switch when behavior depends on one of several named values:
function getLabel(status) {
switch (status) {
case "PENDING":
return "Waiting";
case "APPROVED":
return "Accepted";
case "REJECTED":
return "Declined";
default:
return "Unknown";
}
}
A cleaner version uses the enum object itself.
Configuration and feature flags
Fixed values are also used in config files and application modes:
const Environment = Object.freeze({
DEVELOPMENT: "development",
PRODUCTION: "production",
TEST: "test"
});
Error handling
Enums help standardize error codes or categories:
const ErrorCode = Object.freeze({
NOT_FOUND: "NOT_FOUND",
UNAUTHORIZED: "UNAUTHORIZED"
});
This makes condition handling more consistent than raw strings scattered throughout the project.
Common Mistakes
1. Using a plain object and assuming it is protected
Broken example:
const Color = {
RED: 0,
GREEN: 1,
BLUE: 2
};
Color.RED = 99;
Problem: The value can be changed.
Fix:
const Color = Object.freeze({
RED: 0,
GREEN: 1,
BLUE: 2
});
2. Using == instead of ===
Broken example:
if (currentColor == Color.RED) {
// ...
}
Problem:
== allows type coercion, which can lead to surprising matches.
Fix:
if (currentColor === Color.RED) {
// ...
}
3. Thinking const makes object properties immutable
Broken example:
const Color = {
RED: 0
};
Color.RED = 5;
Problem:
const protects the variable binding, not the object's internal properties.
Fix:
Use Object.freeze() if you want the properties protected.
4. Mixing numeric and string values carelessly
Broken example:
const Status = Object.freeze({
OK: 1,
FAIL: "FAIL"
});
Problem: Mixed value types can make comparisons and debugging harder.
Fix: Choose one style consistently unless you have a clear reason not to.
5. Assuming Object.freeze() is deeply recursive
Broken example:
const Config = Object.freeze({
colors: {
primary: "blue"
}
});
Config.colors.primary = "red";
Problem: The top-level object is frozen, but nested objects are still mutable.
Fix: Freeze nested objects too if needed, or use a deep-freeze helper.
6. Hardcoding raw strings everywhere
Broken example:
if (status === "PENDNG") {
console.log("Waiting");
}
Problem: Typos are easy to miss.
Fix:
const Status = Object.freeze({
PENDING: "PENDING"
});
if (status === Status.PENDING) {
console.log("Waiting");
}
Comparisons
| Approach | Example | Mutable? | Good for | Notes |
|---|---|---|---|---|
| Plain object | { RED: 0 } | Yes | Small quick scripts | Simple, but not protected |
| Frozen object | Object.freeze({ RED: 0 }) | No, at top level | Most JavaScript projects | Common enum-like pattern |
| String enum-like values | { RED: "RED" } | Depends on freeze | Readability and APIs | Easier to debug than numbers |
| Numeric enum-like values | { RED: 0 } | Depends on freeze | Compact internal values | Less descriptive in logs |
const only | const Color = {...} | Properties still mutable | Preventing reassignment only | Not enough for enum safety |
Strings vs numbers
| Style | Example | Pros | Cons |
|---|---|---|---|
| Numbers | RED: 0 | Compact, familiar from other languages | Harder to read in logs and JSON |
| Strings | RED: "RED" | Self-describing, easier to debug | Slightly more verbose |
In many JavaScript applications, string enums are easier to maintain unless numeric values are specifically required.
Cheat Sheet
// Basic enum-like object
const Color = Object.freeze({
RED: "RED",
GREEN: "GREEN",
BLUE: "BLUE"
});
// Usage
if (currentColor === Color.RED) {
console.log("red");
}
Rules
- JavaScript objects are mutable by default.
constdoes not freeze object properties.Object.freeze()makes the top-level object immutable.- Use
===for comparisons. - Prefer strings when readability matters.
- Prefer one consistent value type.
Quick checks
Get all enum values:
Object.values(Color)
Validate a value:
Object.values(Color).includes(currentColor)
Important edge case
Object.freeze() is shallow:
const Example = Object.freeze({
nested: { x: 1 }
});
Example.nested.x = 2; // still possible
Safe pattern
const Status = Object.freeze({
PENDING: "PENDING",
DONE: "DONE"
});
Use enum-like objects for:
- statuses
- roles
- modes
- categories
- state names
FAQ
Does JavaScript have real enums?
Not in older plain JavaScript syntax. The common approach is to use an object as an enum-like container, usually with Object.freeze().
Is const enough to make an enum immutable?
No. const only prevents reassignment of the variable itself. The properties of the object can still change unless you freeze it.
Should I use numbers or strings for enum values in JavaScript?
Strings are often easier to read and debug. Numbers are fine if you specifically need numeric values.
Why use Object.freeze() for enums?
It prevents accidental modification, deletion, or extension of the enum-like object.
Can Object.freeze() protect nested objects too?
No. It only freezes the top level. Nested objects remain mutable unless they are frozen separately.
Is == okay when comparing enum values?
It is better to use === to avoid type coercion and unexpected matches.
How do I check if a value is part of an enum-like object?
Use Object.values():
Object.values(Color).includes(value)
Is a frozen object the standard enum pattern in JavaScript?
Yes, it is one of the most common patterns in plain JavaScript when you want a fixed set of named constants.
Mini Project
Description
Build a small order status module for an application. The project demonstrates how to define a fixed set of allowed status values, protect them from accidental changes, validate incoming data, and use the values in control flow.
Goal
Create and use an immutable enum-like object for order statuses in JavaScript.
Requirements
- Define an enum-like object for order statuses.
- Prevent the status definitions from being modified.
- Write a function that checks whether a given status is valid.
- Write a function that returns a human-readable label for each status.
- Demonstrate usage with both valid and invalid inputs.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.