Question
In TypeScript, using the const keyword directly on a class property causes a compiler error:
class Example {
const MY_CONSTANT = 10;
}
The error is:
A class member cannot have the 'const' keyword.
I want a clear way to express that a class property should not be changed after it is declared, and I want the compiler or IDE to report an error if I try to reassign it.
At the moment, I am using a getter like this:
get MY_CONSTANT(): number {
return 10;
}
Is there a better way to implement class constants in TypeScript, especially in newer versions such as TypeScript 2.x and later?
Short Answer
By the end of this page, you will understand why const cannot be used for class members in TypeScript, and how to model class constants correctly using readonly and static readonly. You will also learn when to use a getter, when to use an instance property, and when to use a class-level constant.
Concept
In TypeScript, const is for variables, not class members. That is why this is valid:
const MAX_RETRIES = 3;
but this is not:
class Config {
const MAX_RETRIES = 3;
}
Class members belong to an object instance or to the class itself, and TypeScript uses different keywords to control whether they can be reassigned.
The main tool for this is readonly.
class User {
readonly id: number;
constructor(id: number) {
this.id = id;
}
}
A readonly property can be assigned:
- where it is declared, or
- inside the constructor of the same class
After that, TypeScript will report an error if you try to change it.
If the value should belong to the class itself rather than each instance, use static readonly:
class AppConfig {
static readonly API_VERSION = 1;
}
This is the closest TypeScript equivalent to a class constant.
Why this matters in real programming:
- It makes intent clear: this value should not change.
- It prevents accidental reassignment.
- It improves autocomplete and tooling.
- It makes shared configuration and fixed values safer.
So the usual answer is:
- use
readonlyfor per-instance values - use
static readonlyfor class-level constants - use
constonly outside classes for local or module-level constants
Mental Model
Think of const, readonly, and static readonly as labels for different kinds of storage:
const= a sealed box sitting on a tablereadonly= a box attached to each object, sealed after setupstatic readonly= one sealed box attached to the class itself, shared by all objects
If every object should have its own unchangeable value, use readonly.
If the value is shared and should act like a constant for the whole class, use static readonly.
A getter is like asking a receptionist for the value every time. That works, but if the value is truly just a fixed constant, static readonly is usually simpler and clearer.
Syntax and Examples
Core syntax
1. readonly instance property
class User {
readonly role: string = "member";
}
This means each User instance has a role property that cannot be reassigned after initialization.
2. readonly assigned in constructor
class User {
readonly id: number;
constructor(id: number) {
this.id = id;
}
}
This is common when the value is only known when the object is created.
3. static readonly class constant
class MathConstants {
static readonly PI = 3.14159;
}
console.log(MathConstants.PI);
This is the most common way to represent a class constant in TypeScript.
4. Getter-based constant
class Example {
get MY_CONSTANT(): number {
return 10;
}
}
This works, but it creates a property accessor rather than a simple constant field. It is more useful when the value is computed rather than fixed.
Beginner-friendly example
class GameSettings {
static readonly MAX_PLAYERS = 4;
readonly createdAt: Date;
constructor() {
this.createdAt = new Date();
}
}
console.log(GameSettings.MAX_PLAYERS);
const settings = new GameSettings();
console.log(settings.createdAt);
What this means
MAX_PLAYERSbelongs to the class, not to individual objects.createdAtbelongs to each instance.- Both are protected from reassignment by TypeScript.
Step by Step Execution
Consider this example:
class ServerConfig {
static readonly DEFAULT_PORT = 3000;
readonly host: string;
constructor(host: string) {
this.host = host;
}
}
const config = new ServerConfig("localhost");
console.log(ServerConfig.DEFAULT_PORT);
console.log(config.host);
// config.host = "example.com"; // Error
// ServerConfig.DEFAULT_PORT = 8080; // Error
Step by step
- The class
ServerConfigis declared. static readonly DEFAULT_PORT = 3000creates a class-level value.- You access it as
ServerConfig.DEFAULT_PORT. - It is shared by all uses of the class.
- You access it as
readonly host: stringdeclares an instance property that cannot be reassigned after setup.- In the constructor,
this.host = hostis allowed because readonly properties may be assigned during construction. new ServerConfig("localhost")creates an object.console.log(ServerConfig.DEFAULT_PORT)prints3000.console.log(config.host)printslocalhost.- Trying to reassign either property causes a TypeScript compile-time error.
This shows the two most important cases:
- class constant:
static readonly - object-specific fixed value:
readonly
Real World Use Cases
Where this is used
Configuration values
class ApiClient {
static readonly BASE_TIMEOUT_MS = 5000;
}
Useful for fixed settings shared across all instances.
Domain identifiers
class Order {
readonly id: string;
constructor(id: string) {
this.id = id;
}
}
An order ID should not change after creation.
Status or role values
class Account {
readonly role = "admin";
}
Good when a property should be set once and then remain stable.
Shared limits and rules
class UploadRules {
static readonly MAX_FILE_SIZE_MB = 20;
}
Useful in validation logic across an app.
Named constants inside a class-based service
class EmailService {
static readonly RETRY_LIMIT = 3;
}
This keeps related constants close to the code that uses them.
Real Codebase Usage
In real projects, developers usually choose one of these patterns:
1. static readonly for constants tied to a class
class CacheService {
static readonly DEFAULT_TTL_SECONDS = 60;
}
This is common when the constant is conceptually part of the class.
2. Module-level const for shared values
const DEFAULT_TTL_SECONDS = 60;
export class CacheService {
getTtl() {
return DEFAULT_TTL_SECONDS;
}
}
This is often preferred when the value does not need to be part of the class API.
3. readonly for values fixed after object creation
class Session {
constructor(public readonly token: string) {}
}
This shorthand is common in constructors.
4. Guarding important object state
class Payment {
constructor(public readonly amount: number) {
if (amount <= 0) {
throw new Error("Amount must be positive");
}
}
}
Developers often combine readonly with validation in constructors.
5. Using getters when the value is derived
class Rectangle {
constructor(private width: number, private height: number) {}
get area(): number {
return this.width * this.height;
}
}
A getter is usually better for calculated values, not fixed constants.
Practical advice
If you are building a real codebase, ask:
- Is this value shared by all instances? Use
static readonly. - Is this value unique per object but immutable after creation? Use
readonly. - Is this value just a file-level constant? Use
constoutside the class. - Is this value computed dynamically? Use a getter.
Common Mistakes
1. Trying to use const inside a class
Broken code:
class Example {
const VALUE = 10;
}
Why it fails:
constis not valid for class members.
Use this instead:
class Example {
static readonly VALUE = 10;
}
2. Using a getter for a simple fixed value
class Example {
get VALUE(): number {
return 10;
}
}
This works, but it may suggest computation or custom logic. If the value is just a constant, static readonly is usually clearer.
3. Forgetting that readonly is compile-time protection
class User {
readonly name = "Ava";
}
TypeScript prevents reassignment in typed code, but runtime JavaScript does not automatically freeze the object.
If you need runtime immutability, you may need Object.freeze() or another runtime strategy.
4. Confusing instance and static members
Broken code:
class Config {
static readonly PORT = 3000;
}
const c = new Config();
console.log(c.PORT); // Error
Fix:
console.log(Config.PORT);
static members belong to the class, not instances.
5. Reassigning a readonly property outside the constructor
Broken code:
class User {
readonly id: number;
constructor(id: number) {
this.id = id;
}
updateId(newId: number) {
this.id = newId; // Error
}
}
Avoid this by assigning readonly values only at declaration or in the constructor.
Comparisons
| Feature | Use case | Can change later? | Belongs to |
|---|---|---|---|
const | Local or module variable | No | Current scope |
readonly | Instance property | No, after initialization | Object instance |
static readonly | Class constant | No, after initialization | Class |
get accessor | Computed or controlled property access | Depends on implementation | Object instance or prototype |
readonly vs getter
| Option | Best for | Notes |
|---|---|---|
readonly | Stored value that should not change | Simple and explicit |
get | Computed value or custom access logic | Good when value is derived |
static readonly vs module-level const
| Option | Best for | Example |
|---|---|---|
static readonly | Constant that conceptually belongs to the class | HttpClient.DEFAULT_TIMEOUT |
module-level const | Constant only needed within the file or module | const DEFAULT_TIMEOUT = 5000 |
A simple rule:
- If the constant is part of the class API, use
static readonly. - If it is just internal support code, a module-level
constmay be cleaner.
Cheat Sheet
// Instance property that cannot be reassigned
class User {
readonly id: number;
constructor(id: number) {
this.id = id;
}
}
// Class constant
class Config {
static readonly PORT = 3000;
}
// Constructor shorthand
class Session {
constructor(public readonly token: string) {}
}
// Module-level constant
const API_URL = "https://example.com";
Rules
constcannot be used on class members.- Use
readonlyfor instance properties. - Use
static readonlyfor class-level constants. - A
readonlyproperty can be assigned:- at declaration
- in the constructor
- Access static members with the class name, not an instance.
Good defaults
- Fixed per object:
readonly - Fixed per class:
static readonly - Fixed per file or function:
const - Computed property:
get
Important edge case
readonly is enforced by TypeScript's type system. It does not automatically make runtime JavaScript objects deeply immutable.
FAQ
Why can't I use const for a class property in TypeScript?
Because const is only for variables in a scope, such as local variables or module-level values. Class members use readonly and static readonly instead.
What is the TypeScript equivalent of a class constant?
Usually static readonly.
class App {
static readonly VERSION = "1.0";
}
Should I use a getter for constants?
Usually no. Use a getter when the value is computed or when access needs custom logic. For a fixed value, readonly or static readonly is clearer.
What is the difference between readonly and const?
const is for variables. readonly is for properties on objects or classes.
Can a readonly property be assigned in the constructor?
Yes. That is one of the normal ways to initialize it.
Does readonly prevent changes at runtime?
Not by itself. It mainly provides compile-time protection in TypeScript.
Should I use static readonly or a top-level const?
Use static readonly if the constant is part of the class API or meaningfully belongs to the class. Use a top-level const if it is just a helper value in the module.
Mini Project
Description
Build a small TypeScript class that models application settings. This project demonstrates the difference between instance-level immutable properties and class-level constants. It is useful because real applications often have fixed limits, version numbers, and IDs that should not be reassigned accidentally.
Goal
Create a ThemeConfig class with both readonly and static readonly properties, then use it safely in code.
Requirements
- Create a class-level constant for the default theme name.
- Create an instance
readonlyproperty for a config ID. - Initialize the readonly instance property through the constructor.
- Create an instance of the class and log both values.
- Verify through code comments which reassignment attempts should fail.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.