javascript
exercises
exercises.js⚡javascript
/**
* ============================================
* 8.4 PRIVATE FIELDS & METHODS - EXERCISES
* ============================================
* Practice encapsulation with private members
*/
/**
* EXERCISE 1: Basic Private Field
* Difficulty: ⭐
*
* Create a Counter class with:
* - Private #count field starting at 0
* - increment() - add 1
* - decrement() - subtract 1
* - reset() - set to 0
* - get value - return current count
*/
function exercise1() {
// YOUR CODE HERE
// TEST CASES
// const counter = new Counter();
// console.log(counter.value); // 0
// counter.increment();
// counter.increment();
// console.log(counter.value); // 2
// counter.decrement();
// console.log(counter.value); // 1
// counter.reset();
// console.log(counter.value); // 0
//
// console.log(counter.count); // undefined (private)
}
/*
* SOLUTION 1:
*
* class Counter {
* #count = 0;
*
* increment() {
* this.#count++;
* return this;
* }
*
* decrement() {
* this.#count--;
* return this;
* }
*
* reset() {
* this.#count = 0;
* return this;
* }
*
* get value() {
* return this.#count;
* }
* }
*/
/**
* EXERCISE 2: Private Method for Validation
* Difficulty: ⭐
*
* Create an Email class with:
* - Private #email field
* - Private #validate(email) method
* - Constructor validates before setting
* - get email - returns the email
*/
function exercise2() {
// YOUR CODE HERE
// TEST CASES
// const email = new Email("test@example.com");
// console.log(email.email); // "test@example.com"
//
// try {
// new Email("invalid"); // Should throw
// } catch (e) {
// console.log("Caught:", e.message);
// }
}
/*
* SOLUTION 2:
*
* class Email {
* #email;
*
* constructor(email) {
* if (!this.#validate(email)) {
* throw new Error("Invalid email format");
* }
* this.#email = email;
* }
*
* #validate(email) {
* return email && email.includes("@") && email.includes(".");
* }
*
* get email() {
* return this.#email;
* }
* }
*/
/**
* EXERCISE 3: Password Storage
* Difficulty: ⭐⭐
*
* Create a User class with:
* - username (public)
* - Private #passwordHash
* - Private #hashPassword(password) method
* - setPassword(password) - hashes and stores
* - verifyPassword(password) - returns boolean
*/
function exercise3() {
// YOUR CODE HERE
// TEST CASES
// const user = new User("alice");
// user.setPassword("secret123");
//
// console.log(user.verifyPassword("secret123")); // true
// console.log(user.verifyPassword("wrong")); // false
//
// console.log(user.username); // "alice"
// console.log(user.passwordHash); // undefined
}
/*
* SOLUTION 3:
*
* class User {
* #passwordHash;
*
* constructor(username) {
* this.username = username;
* }
*
* #hashPassword(password) {
* // Simple hash (use proper crypto in production)
* return Buffer.from(password).toString('base64');
* }
*
* setPassword(password) {
* this.#passwordHash = this.#hashPassword(password);
* }
*
* verifyPassword(password) {
* return this.#hashPassword(password) === this.#passwordHash;
* }
* }
*/
/**
* EXERCISE 4: Wallet with Balance Protection
* Difficulty: ⭐⭐
*
* Create a Wallet class with:
* - Private #balance starting at 0
* - deposit(amount) - adds if positive
* - withdraw(amount) - subtracts if sufficient funds
* - get balance - returns current balance
* - Private #validateAmount(amount) for validation
*/
function exercise4() {
// YOUR CODE HERE
// TEST CASES
// const wallet = new Wallet();
// wallet.deposit(100);
// console.log(wallet.balance); // 100
//
// wallet.withdraw(30);
// console.log(wallet.balance); // 70
//
// try {
// wallet.withdraw(100); // Should throw - insufficient funds
// } catch (e) {
// console.log("Error:", e.message);
// }
//
// try {
// wallet.deposit(-50); // Should throw - invalid amount
// } catch (e) {
// console.log("Error:", e.message);
// }
}
/*
* SOLUTION 4:
*
* class Wallet {
* #balance = 0;
*
* #validateAmount(amount) {
* if (typeof amount !== 'number' || amount <= 0) {
* throw new Error("Amount must be a positive number");
* }
* }
*
* deposit(amount) {
* this.#validateAmount(amount);
* this.#balance += amount;
* return this.#balance;
* }
*
* withdraw(amount) {
* this.#validateAmount(amount);
* if (amount > this.#balance) {
* throw new Error("Insufficient funds");
* }
* this.#balance -= amount;
* return amount;
* }
*
* get balance() {
* return this.#balance;
* }
* }
*/
/**
* EXERCISE 5: Private Static Counter
* Difficulty: ⭐⭐
*
* Create a Product class with:
* - Private static #nextId starting at 1
* - Private #id assigned from #nextId
* - name (public)
* - get id - returns the private id
* - Static getNextId() - returns next id to be assigned
*/
function exercise5() {
// YOUR CODE HERE
// TEST CASES
// const p1 = new Product("Apple");
// const p2 = new Product("Banana");
// const p3 = new Product("Cherry");
//
// console.log(p1.id); // 1
// console.log(p2.id); // 2
// console.log(p3.id); // 3
//
// console.log(Product.getNextId()); // 4
}
/*
* SOLUTION 5:
*
* class Product {
* static #nextId = 1;
* #id;
*
* constructor(name) {
* this.#id = Product.#nextId++;
* this.name = name;
* }
*
* get id() {
* return this.#id;
* }
*
* static getNextId() {
* return Product.#nextId;
* }
* }
*/
/**
* EXERCISE 6: Token Manager
* Difficulty: ⭐⭐⭐
*
* Create a TokenManager class with:
* - Private static #tokens Map
* - Private static #generateToken() method
* - Static createToken(userId) - creates and stores token
* - Static validateToken(token) - returns userId or null
* - Static revokeToken(token) - removes token
*/
function exercise6() {
// YOUR CODE HERE
// TEST CASES
// const token = TokenManager.createToken("user123");
// console.log("Token:", token); // Random token string
//
// console.log(TokenManager.validateToken(token)); // "user123"
// console.log(TokenManager.validateToken("fake")); // null
//
// TokenManager.revokeToken(token);
// console.log(TokenManager.validateToken(token)); // null
}
/*
* SOLUTION 6:
*
* class TokenManager {
* static #tokens = new Map();
*
* static #generateToken() {
* const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
* let token = '';
* for (let i = 0; i < 32; i++) {
* token += chars.charAt(Math.floor(Math.random() * chars.length));
* }
* return token;
* }
*
* static createToken(userId) {
* const token = this.#generateToken();
* this.#tokens.set(token, userId);
* return token;
* }
*
* static validateToken(token) {
* return this.#tokens.get(token) || null;
* }
*
* static revokeToken(token) {
* return this.#tokens.delete(token);
* }
* }
*/
/**
* EXERCISE 7: Stack with Private Storage
* Difficulty: ⭐⭐⭐
*
* Create a Stack class with:
* - Private #items array
* - Private #maxSize limit
* - push(item) - add to top (throw if full)
* - pop() - remove from top (throw if empty)
* - peek() - look at top without removing
* - get size, get isEmpty, get isFull
*/
function exercise7() {
// YOUR CODE HERE
// TEST CASES
// const stack = new Stack(3); // Max 3 items
//
// stack.push(1);
// stack.push(2);
// console.log(stack.peek()); // 2
// console.log(stack.size); // 2
//
// stack.push(3);
// console.log(stack.isFull); // true
//
// try {
// stack.push(4); // Should throw
// } catch (e) {
// console.log("Error:", e.message);
// }
//
// console.log(stack.pop()); // 3
// console.log(stack.isEmpty); // false
}
/*
* SOLUTION 7:
*
* class Stack {
* #items = [];
* #maxSize;
*
* constructor(maxSize = Infinity) {
* this.#maxSize = maxSize;
* }
*
* push(item) {
* if (this.isFull) {
* throw new Error("Stack overflow");
* }
* this.#items.push(item);
* return this;
* }
*
* pop() {
* if (this.isEmpty) {
* throw new Error("Stack underflow");
* }
* return this.#items.pop();
* }
*
* peek() {
* return this.#items[this.#items.length - 1];
* }
*
* get size() {
* return this.#items.length;
* }
*
* get isEmpty() {
* return this.#items.length === 0;
* }
*
* get isFull() {
* return this.#items.length >= this.#maxSize;
* }
* }
*/
/**
* EXERCISE 8: Type Checking with in Operator
* Difficulty: ⭐⭐⭐
*
* Create a SecureData class with:
* - Private #data field
* - Private #accessKey field
* - getData(key) - returns data if key matches
* - Static isSecureData(obj) - checks if obj has #data
*/
function exercise8() {
// YOUR CODE HERE
// TEST CASES
// const secure = new SecureData("secret", "key123");
//
// console.log(secure.getData("key123")); // "secret"
// console.log(secure.getData("wrong")); // null
//
// console.log(SecureData.isSecureData(secure)); // true
// console.log(SecureData.isSecureData({})); // false
// console.log(SecureData.isSecureData({ data: "fake" })); // false
}
/*
* SOLUTION 8:
*
* class SecureData {
* #data;
* #accessKey;
*
* constructor(data, accessKey) {
* this.#data = data;
* this.#accessKey = accessKey;
* }
*
* getData(key) {
* if (key !== this.#accessKey) {
* return null;
* }
* return this.#data;
* }
*
* static isSecureData(obj) {
* return #data in obj;
* }
* }
*/
/**
* EXERCISE 9: Private Event System
* Difficulty: ⭐⭐⭐⭐
*
* Create an Observable class with:
* - Private #observers Set
* - Private #value
* - subscribe(fn) - adds observer, returns unsubscribe fn
* - Private #notify() method
* - set value(v) - sets value and notifies
* - get value - returns current value
*/
function exercise9() {
// YOUR CODE HERE
// TEST CASES
// const obs = new Observable(0);
//
// const unsub1 = obs.subscribe((val) => console.log("Observer 1:", val));
// const unsub2 = obs.subscribe((val) => console.log("Observer 2:", val));
//
// obs.value = 10;
// // Logs:
// // Observer 1: 10
// // Observer 2: 10
//
// unsub1();
// obs.value = 20;
// // Logs:
// // Observer 2: 20
}
/*
* SOLUTION 9:
*
* class Observable {
* #observers = new Set();
* #value;
*
* constructor(initialValue) {
* this.#value = initialValue;
* }
*
* #notify() {
* for (const observer of this.#observers) {
* observer(this.#value);
* }
* }
*
* subscribe(fn) {
* this.#observers.add(fn);
* return () => this.#observers.delete(fn);
* }
*
* get value() {
* return this.#value;
* }
*
* set value(v) {
* this.#value = v;
* this.#notify();
* }
* }
*/
/**
* EXERCISE 10: Rate Limiter
* Difficulty: ⭐⭐⭐⭐
*
* Create a RateLimiter class with:
* - Private #requests Map (key -> timestamps array)
* - Private #limit (max requests)
* - Private #windowMs (time window in ms)
* - Private #cleanup(key) method
* - isAllowed(key) - returns boolean
* - record(key) - records a request
*/
function exercise10() {
// YOUR CODE HERE
// TEST CASES
// const limiter = new RateLimiter(3, 1000); // 3 requests per second
//
// console.log(limiter.isAllowed("user1")); // true
// limiter.record("user1");
// limiter.record("user1");
// limiter.record("user1");
//
// console.log(limiter.isAllowed("user1")); // false (3 requests already)
// console.log(limiter.isAllowed("user2")); // true (different user)
//
// setTimeout(() => {
// console.log(limiter.isAllowed("user1")); // true (window passed)
// }, 1100);
}
/*
* SOLUTION 10:
*
* class RateLimiter {
* #requests = new Map();
* #limit;
* #windowMs;
*
* constructor(limit, windowMs) {
* this.#limit = limit;
* this.#windowMs = windowMs;
* }
*
* #cleanup(key) {
* const now = Date.now();
* const timestamps = this.#requests.get(key) || [];
* const valid = timestamps.filter(t => now - t < this.#windowMs);
* this.#requests.set(key, valid);
* return valid;
* }
*
* isAllowed(key) {
* const timestamps = this.#cleanup(key);
* return timestamps.length < this.#limit;
* }
*
* record(key) {
* if (!this.#requests.has(key)) {
* this.#requests.set(key, []);
* }
* this.#requests.get(key).push(Date.now());
* }
* }
*/
/**
* EXERCISE 11: Singleton with Private Constructor Logic
* Difficulty: ⭐⭐⭐⭐
*
* Create a Logger singleton with:
* - Private static #instance
* - Private #logs array
* - Private #formatMessage(level, msg) method
* - Static getInstance() - returns singleton
* - log(msg), warn(msg), error(msg) methods
* - getLogs() - returns all logs
*/
function exercise11() {
// YOUR CODE HERE
// TEST CASES
// const logger1 = Logger.getInstance();
// const logger2 = Logger.getInstance();
//
// console.log(logger1 === logger2); // true
//
// logger1.log("Application started");
// logger1.warn("Memory usage high");
// logger2.error("Connection failed");
//
// console.log(logger1.getLogs());
// // [
// // { level: 'LOG', message: '...', timestamp: '...' },
// // { level: 'WARN', message: '...', timestamp: '...' },
// // { level: 'ERROR', message: '...', timestamp: '...' }
// // ]
}
/*
* SOLUTION 11:
*
* class Logger {
* static #instance = null;
* #logs = [];
*
* constructor() {
* if (Logger.#instance) {
* return Logger.#instance;
* }
* Logger.#instance = this;
* }
*
* static getInstance() {
* if (!Logger.#instance) {
* new Logger();
* }
* return Logger.#instance;
* }
*
* #formatMessage(level, msg) {
* return {
* level,
* message: msg,
* timestamp: new Date().toISOString()
* };
* }
*
* log(msg) {
* this.#logs.push(this.#formatMessage('LOG', msg));
* }
*
* warn(msg) {
* this.#logs.push(this.#formatMessage('WARN', msg));
* }
*
* error(msg) {
* this.#logs.push(this.#formatMessage('ERROR', msg));
* }
*
* getLogs() {
* return [...this.#logs];
* }
* }
*/
/**
* EXERCISE 12: State Machine with Private Transitions
* Difficulty: ⭐⭐⭐⭐
*
* Create a TrafficLight class with:
* - Private #state ('red', 'yellow', 'green')
* - Private #transitions object defining valid transitions
* - Private #canTransition(from, to) method
* - change() - cycles to next state
* - get state - returns current state
*/
function exercise12() {
// YOUR CODE HERE
// TEST CASES
// const light = new TrafficLight();
// console.log(light.state); // "red"
//
// light.change();
// console.log(light.state); // "green"
//
// light.change();
// console.log(light.state); // "yellow"
//
// light.change();
// console.log(light.state); // "red"
}
/*
* SOLUTION 12:
*
* class TrafficLight {
* #state = 'red';
* #transitions = {
* red: 'green',
* green: 'yellow',
* yellow: 'red'
* };
*
* #canTransition(from, to) {
* return this.#transitions[from] === to;
* }
*
* change() {
* const nextState = this.#transitions[this.#state];
* if (this.#canTransition(this.#state, nextState)) {
* this.#state = nextState;
* }
* return this;
* }
*
* get state() {
* return this.#state;
* }
* }
*/
/**
* EXERCISE 13: Private Memoization
* Difficulty: ⭐⭐⭐⭐
*
* Create a Fibonacci class with:
* - Private static #cache Map
* - Private static #compute(n) method (recursive)
* - Static calculate(n) - memoized fibonacci
* - Static clearCache() - clears memoization
* - Static get cacheSize
*/
function exercise13() {
// YOUR CODE HERE
// TEST CASES
// console.log(Fibonacci.calculate(10)); // 55
// console.log(Fibonacci.calculate(20)); // 6765
// console.log(Fibonacci.calculate(40)); // 102334155
//
// console.log(Fibonacci.cacheSize); // Number of cached values
//
// Fibonacci.clearCache();
// console.log(Fibonacci.cacheSize); // 0
}
/*
* SOLUTION 13:
*
* class Fibonacci {
* static #cache = new Map([[0, 0], [1, 1]]);
*
* static #compute(n) {
* if (this.#cache.has(n)) {
* return this.#cache.get(n);
* }
*
* const result = this.#compute(n - 1) + this.#compute(n - 2);
* this.#cache.set(n, result);
* return result;
* }
*
* static calculate(n) {
* if (n < 0) throw new Error("n must be non-negative");
* return this.#compute(n);
* }
*
* static clearCache() {
* this.#cache = new Map([[0, 0], [1, 1]]);
* }
*
* static get cacheSize() {
* return this.#cache.size;
* }
* }
*/
/**
* EXERCISE 14: Connection Pool
* Difficulty: ⭐⭐⭐⭐⭐
*
* Create a ConnectionPool class with:
* - Private #available array
* - Private #inUse Set
* - Private #maxSize
* - Private #createConnection() method
* - acquire() - get connection (create if needed)
* - release(conn) - return connection to pool
* - get stats - return { available, inUse, total }
*/
function exercise14() {
// YOUR CODE HERE
// TEST CASES
// const pool = new ConnectionPool(3);
//
// const conn1 = pool.acquire();
// const conn2 = pool.acquire();
// console.log(pool.stats); // { available: 0, inUse: 2, total: 2 }
//
// pool.release(conn1);
// console.log(pool.stats); // { available: 1, inUse: 1, total: 2 }
//
// const conn3 = pool.acquire(); // Reuses conn1
// console.log(conn3 === conn1); // true
//
// pool.acquire(); // Creates new (total now 3)
//
// try {
// pool.acquire(); // Should throw - pool exhausted
// } catch (e) {
// console.log("Error:", e.message);
// }
}
/*
* SOLUTION 14:
*
* class ConnectionPool {
* #available = [];
* #inUse = new Set();
* #maxSize;
* #connectionId = 0;
*
* constructor(maxSize) {
* this.#maxSize = maxSize;
* }
*
* #createConnection() {
* return {
* id: ++this.#connectionId,
* createdAt: new Date()
* };
* }
*
* acquire() {
* let conn;
*
* if (this.#available.length > 0) {
* conn = this.#available.pop();
* } else if (this.#inUse.size < this.#maxSize) {
* conn = this.#createConnection();
* } else {
* throw new Error("Connection pool exhausted");
* }
*
* this.#inUse.add(conn);
* return conn;
* }
*
* release(conn) {
* if (this.#inUse.has(conn)) {
* this.#inUse.delete(conn);
* this.#available.push(conn);
* }
* }
*
* get stats() {
* return {
* available: this.#available.length,
* inUse: this.#inUse.size,
* total: this.#available.length + this.#inUse.size
* };
* }
* }
*/
/**
* EXERCISE 15: Secure Key-Value Store
* Difficulty: ⭐⭐⭐⭐⭐
*
* Create a SecureStore class with:
* - Private #data Map
* - Private #masterKey
* - Private #encrypt(value) and #decrypt(value) methods
* - Private #verifyKey(key) method
* - set(key, value, masterKey) - encrypts and stores
* - get(key, masterKey) - decrypts and returns
* - has(key, masterKey) - checks existence
* - delete(key, masterKey) - removes entry
*/
function exercise15() {
// YOUR CODE HERE
// TEST CASES
// const store = new SecureStore("masterSecret");
//
// store.set("apiKey", "sk-12345", "masterSecret");
// store.set("dbPassword", "p@ssw0rd", "masterSecret");
//
// console.log(store.get("apiKey", "masterSecret")); // "sk-12345"
// console.log(store.has("dbPassword", "masterSecret")); // true
//
// try {
// store.get("apiKey", "wrongKey"); // Should throw
// } catch (e) {
// console.log("Error:", e.message);
// }
//
// store.delete("apiKey", "masterSecret");
// console.log(store.has("apiKey", "masterSecret")); // false
}
/*
* SOLUTION 15:
*
* class SecureStore {
* #data = new Map();
* #masterKey;
*
* constructor(masterKey) {
* this.#masterKey = masterKey;
* }
*
* #verifyKey(key) {
* if (key !== this.#masterKey) {
* throw new Error("Invalid master key");
* }
* }
*
* #encrypt(value) {
* // Simple encryption (use proper crypto in production)
* return Buffer.from(JSON.stringify(value)).toString('base64');
* }
*
* #decrypt(encrypted) {
* return JSON.parse(Buffer.from(encrypted, 'base64').toString());
* }
*
* set(key, value, masterKey) {
* this.#verifyKey(masterKey);
* this.#data.set(key, this.#encrypt(value));
* }
*
* get(key, masterKey) {
* this.#verifyKey(masterKey);
* const encrypted = this.#data.get(key);
* if (!encrypted) return undefined;
* return this.#decrypt(encrypted);
* }
*
* has(key, masterKey) {
* this.#verifyKey(masterKey);
* return this.#data.has(key);
* }
*
* delete(key, masterKey) {
* this.#verifyKey(masterKey);
* return this.#data.delete(key);
* }
* }
*/
// Run exercises
console.log('=== Private Fields Exercises ===\n');
// Uncomment to run individual exercises
// exercise1();
// exercise2();
// exercise3();
// exercise4();
// exercise5();
// exercise6();
// exercise7();
// exercise8();
// exercise9();
// exercise10();
// exercise11();
// exercise12();
// exercise13();
// exercise14();
// exercise15();
console.log('\nUncomment exercises to practice!');