javascript

exercises

exercises.js
/**
 * ============================================
 * 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!');
Exercises - JavaScript Tutorial | DeepML