-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
148: Member Visibility: public, private, protected
By now, you're comfortable with classes and how to instantiate them. But as your projects grow, you'll find that letting any part of your code touch any property of a class is a recipe for disaster. That's where member visibility comes in. It's essentially your way of drawing boundaries around your data.
Do I really need to mark everything as public?
The short answer is: no. In TypeScript, if you don't specify a visibility modifier, it defaults to public. I usually omit the public keyword entirely because it just adds noise to the code. If a property is public, it means any piece of code that has a reference to the instance can read or change it.
class GameCharacter {
name: string; // implicitly public
health: number = 100; // implicitly public
constructor(name: string) {
this.name = name;
}
}
const hero = new GameCharacter("Aragorn");
hero.health = 0; // This works, but maybe we don't want it to be this easy?
When should I use private versus protected?
This is where most people get tripped up. Both prevent outside code from touching your properties, but they treat "children" (subclasses) differently. Use private when a property is a secret that only that specific class should know about. Use protected when you want your subclasses to have access, but you still want to keep the rest of the world out.
Think about a character's internal ID versus their stamina. The ID should be private; the stamina might be something a Warrior subclass needs to modify during a "Power Attack," but the MainMenu shouldn't be able to just set it to 999.
class GameCharacter {
private internalId: string = "ID_12345";
protected stamina: number = 50;
public getInfo() {
console.log(`ID: ${this.internalId}`); // Works: inside the class
}
}
class Warrior extends GameCharacter {
public attack() {
this.stamina -= 10; // Works: stamina is protected
// this.internalId = "NEW_ID"; // Error: internalId is private to GameCharacter
}
}
const myWarrior = new Warrior();
// myWarrior.stamina = 100; // Error: stamina is protected and not accessible outside the class hierarchy
Does the private keyword actually stop someone from hacking the data at runtime?
Here is the "gotcha" that catches a lot of developers: TypeScript's private and protected modifiers are compile-time checks. Once the code is transpiled to JavaScript, those modifiers vanish. If someone is determined enough, they can still access those properties in the browser console.
If you need "hard" privacy that exists even in the compiled JavaScript, you should use the JavaScript private class fields syntax (the # prefix). I generally stick to the TypeScript private keyword for internal team organization, but I use # when I'm building a library where I absolutely cannot allow the end-user to touch a specific variable.
class BankAccount {
#secretPin: string; // Hard runtime privacy
constructor(pin: string) {
this.#secretPin = pin;
}
}
const account = new BankAccount("1234");
// console.log(account.#secretPin); // This is a syntax error in both TS and JS
📋 Practical Task
Implementing a Protected Inventory System for RPG Characters
You are building a combat system. Your goal is to ensure that a character's inventory can be modified by the character and their subclasses, but not by the external game engine logic.
- Create a base class
Characterwith aprotectedarray calledinventory(strings) and aprivateproperty calledcharacterId. - Create a subclass
Magethat extendsCharacter. - In the
Mageclass, implement a methodcastSpell(spellName: string)that adds the spell name to theinventoryarray. - In the
Mageclass, try to create a method that changes thecharacterIdand observe the TypeScript error. - Instantiate a
Mageobject and attempt to push an item directly into theinventoryarray from outside the class to confirm it is blocked.
There are no comments for now.