# TypeScript private vs Native JS Private Fields (#)

**TypeScript's `private` keyword only enforces access restrictions during compilation, leaving properties visible at runtime and during serialization like `JSON.stringify()`. To achieve true runtime privacy, you must use JavaScript's native `#` private fields introduced in ES2022, which natively protect data at both compile-time and runtime.**

Imagine you are building a payment integration and store an API key inside a client class. You add TypeScript's `private` modifier to the property, feeling confident that your key is safe from unintended access. But the second you serialize that class instance or inspect it in your browser's developer console, your secret key is displayed in plain text.

This happens because TypeScript's type system disappears the moment your code compiles down to JavaScript.

## Why is TypeScript `private` not private at runtime?

TypeScript's `private` modifier is an abstraction that exists purely to provide feedback inside your IDE and during the build step. Once your code is transpiled into plain JavaScript, the `private` keyword is completely removed, converting the property into a standard public property on the object.

Think of TypeScript as a security guard standing outside a building checking badges. Once you pass the guard and step inside the building (the JavaScript runtime), the guard is gone. If another piece of code inspects your object at runtime using `JSON.stringify()` or `Object.keys()`, it bypasses all TypeScript checks.

Here is what happens when you serialize an object using TypeScript's `private` modifier:

```typescript
class AuthToken {
  private secretKey = "shh-secret-value";
}

const token = new AuthToken();
console.log(JSON.stringify(token));
// Output: {"secretKey":"shh-secret-value"}
```

Because the transpiler strips the `private` keyword away, the property remains fully enumerable and visible at runtime.

## How do JavaScript `#` private fields guarantee runtime privacy?

ECMAScript 2022 introduced native private class fields using the `#` prefix, which are enforced directly by the JavaScript engine at runtime. When you prefix a property with `#`, the runtime hides it from external access, reflection, and enumeration.

Instead of relying on build-step type checking, the JavaScript runtime allocates private fields using a hard-coded internal metadata structure. If external code attempts to access a `#` property, the engine throws a runtime `TypeError` immediately.

Let's update our example to use native private fields:

```typescript
class AuthToken {
  #secretKey = "shh-secret-value";
}

const token = new AuthToken();
console.log(JSON.stringify(token));
// Output: {}
```

Because `#secretKey` is enforced natively at runtime, `JSON.stringify()` completely ignores the property, preserving your data boundaries.

## What is the difference between `private` and `#` private fields?

Choosing between `private` and `#` comes down to whether you need design-time type safety or true runtime isolation. 

| Feature | TypeScript `private` | JS Private Fields (`#`) |
| :--- | :--- | :--- |
| **Enforcement Phase** | Compile-time only | Compile-time and Runtime |
| **`JSON.stringify()`** | Included in output | Excluded from output |
| **`Object.keys()` Visibility** | Enumerable / Visible | Completely hidden |
| **Bypass via Type Casting** | Possible (`(obj as any)`) | Impossible |
| **Minimum Target** | Any ES version | ES2022 (or polyfilled) |

If you simply want to signal to teammates that a property shouldn't be touched directly, the `private` keyword is often enough. But if you are handling sensitive credentials, internal state machines, or library internals that must never be exposed or serialized, native `#` private fields are essential.

## Frequently Asked Questions

### Can you bypass JavaScript `#` private fields using `any` in TypeScript?
No, you cannot bypass `#` private fields using type casting like `(instance as any).#field`. Because runtime privacy is enforced directly by the V8 engine, attempting to access a private field outside its declaring class will throw an uncatchable syntax or type error.

### Do `#` private fields show up in `Object.keys()` or `for...in` loops?
No, native `#` private fields do not appear in `Object.keys()`, `Object.getOwnPropertyNames()`, or `for...in` loops. They are omitted from object key lists entirely.

### Does using native `#` private fields affect performance?
Modern JavaScript engines like V8 optimize `#` private fields aggressively. While historical polyfills added runtime overhead, modern native implementations run with virtually identical performance to standard class properties.
