Menu

TypeScript Access Modifiers: public, private, protected

TypeScript has three access modifiers, public, private and protected, plus readonly. Learn what each allows, why TypeScript private is a compile-time check while JavaScript #private fields are enforced at runtime, and which to choose.

This page includes runnable editors - edit, run, and see output instantly.

TypeScript has three access modifiers for class members: public (the default), protected and private. They control where a member can be used, and the compiler reports any access from the wrong place.

The last line is a compile error, but look at what it printed when run anyway: 123-45-6789. That is the most important fact on this page, and the private section explains it.

What Each Modifier Allows

ModifierInside the classIn a subclassFrom outsideEnforced at runtime
public (default)yesyesyesnothing to enforce
protectedyesyesnono
privateyesnonono
#name (JavaScript)yesnonoyes
readonlyread, and write in the constructorreadreadno

The modifiers work on fields, methods, getters and setters, constructors and parameter properties (constructor(private id: string)). Writing public is optional, and many codebases leave it out.

private Is a Compile-Time Check

TypeScript erases private along with the rest of the types. The compiled class has an ordinary property, so anything that does not go through the type checker sees it: plain JavaScript callers, JSON.stringify, Object.keys, and even TypeScript's own bracket notation, which is allowed as a deliberate escape hatch.

That is fine for what private is for: telling other developers (and your editor) that a member is an implementation detail. It is not a security boundary, and a private field will end up in logs and JSON responses unless you strip it yourself.

#private Fields: Enforced at Runtime

JavaScript has its own private fields, written with #. The engine enforces them: outside the class, obj.#field is not even valid syntax, and the field does not show up in Object.keys, JSON.stringify or console.log.

Writing s.#token outside the class is error TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), and unlike the private case, there is no way around it at runtime either. See private fields in JavaScript for the runtime rules.

private vs #private: Which to Use

private x#x
Checked bythe compilerthe JavaScript engine
Visible to Object.keys / JSON.stringifyyesno
Bracket access obj["x"]allowednot possible
Subclass can declare its own xno, it conflictsyes, each class has its own #x
Copied by a spread { ...obj }yesno
Syntax on methodsprivate helper()#helper()

Use #private when the data must stay private at runtime (tokens, internal state a library's users must not touch) or when you want JSON.stringify to leave it out. Use private when the goal is only a clean public API, when a framework needs to read the field reflectively, or to match a codebase's existing style. Do not combine them: private #x is error TS18010 (An accessibility modifier cannot be used with a private identifier).

protected and Subclasses

A protected member is available inside the class and in any class that extends it, but not on instances from outside.

One rule surprises people. Inside Polygon, you can read sides on this or on another Polygon, but not on a plain Shape passed in as a parameter: other.sides where other: Shape is error TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). A subclass may only reach protected members of objects that belong to its own branch of the hierarchy.

A subclass can make a protected member public by redeclaring it, but it cannot make a public member protected or private. Redeclare it with an initializer (public override sides = 6) or as a type only (declare public sides: number). A bare public sides: number; is rejected with TS2564 and TS2612, because under modern class fields it would reset the inherited value to undefined after super() returns.

readonly

readonly blocks reassignment after construction. The field can be set in its declaration or in the constructor; any later assignment is error TS2540.

Two limits show up here. readonly is shallow: the array cannot be replaced, but its contents can change (type it readonly string[] to block push). And like private, it disappears at runtime, so the assignment the compiler rejected still ran. For a value that must not change at runtime, use Object.freeze or a getter with no setter. The readonly page covers Readonly<T> and readonly arrays.

readonly combines with the access modifiers: private readonly cache = new Map<string, number>() is a common pattern for a field that is internal and never reassigned.

Common Mistakes

  • Treating private as security. It is gone at runtime. Use #field for data that must stay hidden, and never send an object with secrets straight to JSON.stringify.
  • Writing public everywhere. It is the default; adding it changes nothing.
  • Using protected for everything "internal". If no subclass needs it, private keeps the surface smaller.
  • Expecting readonly to freeze nested data. It only stops reassignment of the property itself.

Frequently Asked Questions

What are the access modifiers in TypeScript?

public (the default: accessible everywhere), protected (inside the class and its subclasses) and private (inside the class only). readonly is a separate modifier that blocks reassignment after the constructor and combines with any of the three.

What is the difference between private and #private in TypeScript?

private is checked by the compiler only; the emitted JavaScript has an ordinary property, so obj["secret"], JSON.stringify and Object.keys still see it. #secret is a JavaScript private field: the runtime enforces it, and code outside the class cannot read it at all.

Is private in TypeScript really private?

Only at compile time. After compilation the field is a normal property that any JavaScript code can read. TypeScript even allows bracket access (obj["field"]) to private members as an escape hatch. Use #field when privacy has to hold at runtime.

What is the difference between protected and private in TypeScript?

A private member is visible only inside the class that declares it. A protected member is also visible inside subclasses. Neither can be accessed on an instance from outside the class hierarchy.

Can readonly properties be changed in TypeScript?

A readonly property can be assigned in its declaration or in the constructor, and nowhere else (TS2540 otherwise). It is shallow: a readonly tags: string[] cannot be replaced, but tags.push() still works. Use readonly string[] to block that too.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED