javascript

exercises

exercises.js
/**
 * ============================================
 * 8.3 STATIC MEMBERS - EXERCISES
 * ============================================
 * Practice static properties, methods, and patterns
 */

/**
 * EXERCISE 1: Basic Static Method
 * Difficulty: ⭐
 *
 * Create a StringUtils class with static methods:
 * - capitalize(str) - capitalize first letter
 * - reverse(str) - reverse the string
 * - isEmpty(str) - check if null, undefined, or empty
 */
function exercise1() {
  // YOUR CODE HERE
  // TEST CASES
  // console.log(StringUtils.capitalize("hello"));  // "Hello"
  // console.log(StringUtils.reverse("hello"));     // "olleh"
  // console.log(StringUtils.isEmpty(""));          // true
  // console.log(StringUtils.isEmpty(null));        // true
  // console.log(StringUtils.isEmpty("hello"));     // false
}

/*
 * SOLUTION 1:
 *
 * class StringUtils {
 *     static capitalize(str) {
 *         if (!str) return str;
 *         return str.charAt(0).toUpperCase() + str.slice(1);
 *     }
 *
 *     static reverse(str) {
 *         return str.split('').reverse().join('');
 *     }
 *
 *     static isEmpty(str) {
 *         return str == null || str.length === 0;
 *     }
 * }
 */

/**
 * EXERCISE 2: Static Counter
 * Difficulty: ⭐
 *
 * Create an Item class where:
 * - Each instance gets a unique auto-incrementing id
 * - Static getCount() returns total items created
 * - Static reset() resets the counter
 */
function exercise2() {
  // YOUR CODE HERE
  // TEST CASES
  // const item1 = new Item("Apple");
  // const item2 = new Item("Banana");
  // const item3 = new Item("Cherry");
  //
  // console.log(item1.id);  // 1
  // console.log(item2.id);  // 2
  // console.log(item3.id);  // 3
  // console.log(Item.getCount());  // 3
  //
  // Item.reset();
  // const item4 = new Item("Date");
  // console.log(item4.id);  // 1
}

/*
 * SOLUTION 2:
 *
 * class Item {
 *     static #count = 0;
 *
 *     constructor(name) {
 *         this.id = ++Item.#count;
 *         this.name = name;
 *     }
 *
 *     static getCount() {
 *         return Item.#count;
 *     }
 *
 *     static reset() {
 *         Item.#count = 0;
 *     }
 * }
 */

/**
 * EXERCISE 3: Factory Pattern
 * Difficulty: ⭐⭐
 *
 * Create a Vehicle class with:
 * - Constructor taking type, make, model
 * - Static factory methods: createCar(), createTruck(), createMotorcycle()
 * - Each factory sets appropriate type automatically
 */
function exercise3() {
  // YOUR CODE HERE
  // TEST CASES
  // const car = Vehicle.createCar("Toyota", "Camry");
  // console.log(car.type);   // "car"
  // console.log(car.make);   // "Toyota"
  // console.log(car.model);  // "Camry"
  //
  // const truck = Vehicle.createTruck("Ford", "F-150");
  // console.log(truck.type); // "truck"
  //
  // const bike = Vehicle.createMotorcycle("Honda", "CBR");
  // console.log(bike.type);  // "motorcycle"
}

/*
 * SOLUTION 3:
 *
 * class Vehicle {
 *     constructor(type, make, model) {
 *         this.type = type;
 *         this.make = make;
 *         this.model = model;
 *     }
 *
 *     static createCar(make, model) {
 *         return new Vehicle("car", make, model);
 *     }
 *
 *     static createTruck(make, model) {
 *         return new Vehicle("truck", make, model);
 *     }
 *
 *     static createMotorcycle(make, model) {
 *         return new Vehicle("motorcycle", make, model);
 *     }
 * }
 */

/**
 * EXERCISE 4: Static Configuration
 * Difficulty: ⭐⭐
 *
 * Create an App class with:
 * - Static config object with defaults
 * - Static configure(options) to merge settings
 * - Static get(key) to retrieve a setting
 * - Static reset() to restore defaults
 */
function exercise4() {
  // YOUR CODE HERE
  // TEST CASES
  // console.log(App.get("theme"));     // "light" (default)
  // console.log(App.get("language"));  // "en" (default)
  //
  // App.configure({ theme: "dark", fontSize: 14 });
  // console.log(App.get("theme"));     // "dark"
  // console.log(App.get("fontSize"));  // 14
  //
  // App.reset();
  // console.log(App.get("theme"));     // "light"
  // console.log(App.get("fontSize"));  // undefined
}

/*
 * SOLUTION 4:
 *
 * class App {
 *     static #defaults = {
 *         theme: "light",
 *         language: "en"
 *     };
 *
 *     static #config = { ...App.#defaults };
 *
 *     static configure(options) {
 *         this.#config = { ...this.#config, ...options };
 *     }
 *
 *     static get(key) {
 *         return this.#config[key];
 *     }
 *
 *     static reset() {
 *         this.#config = { ...this.#defaults };
 *     }
 * }
 */

/**
 * EXERCISE 5: Static Validation
 * Difficulty: ⭐⭐
 *
 * Create a Validator class with static methods:
 * - isNumber(value) - check if valid number
 * - isInRange(value, min, max) - check if in range
 * - isPositive(value) - check if positive number
 * - isInteger(value) - check if integer
 */
function exercise5() {
  // YOUR CODE HERE
  // TEST CASES
  // console.log(Validator.isNumber(42));        // true
  // console.log(Validator.isNumber("42"));      // false
  // console.log(Validator.isNumber(NaN));       // false
  //
  // console.log(Validator.isInRange(5, 1, 10)); // true
  // console.log(Validator.isInRange(0, 1, 10)); // false
  //
  // console.log(Validator.isPositive(5));       // true
  // console.log(Validator.isPositive(-1));      // false
  //
  // console.log(Validator.isInteger(5));        // true
  // console.log(Validator.isInteger(5.5));      // false
}

/*
 * SOLUTION 5:
 *
 * class Validator {
 *     static isNumber(value) {
 *         return typeof value === 'number' && !Number.isNaN(value);
 *     }
 *
 *     static isInRange(value, min, max) {
 *         return this.isNumber(value) && value >= min && value <= max;
 *     }
 *
 *     static isPositive(value) {
 *         return this.isNumber(value) && value > 0;
 *     }
 *
 *     static isInteger(value) {
 *         return this.isNumber(value) && Number.isInteger(value);
 *     }
 * }
 */

/**
 * EXERCISE 6: Singleton Pattern
 * Difficulty: ⭐⭐⭐
 *
 * Create a Database singleton class:
 * - Only one instance can exist
 * - Static getInstance() returns the singleton
 * - Has connect() and query(sql) methods
 * - Tracks if connected
 */
function exercise6() {
  // YOUR CODE HERE
  // TEST CASES
  // const db1 = Database.getInstance();
  // const db2 = Database.getInstance();
  //
  // console.log(db1 === db2);  // true
  //
  // db1.connect();
  // console.log(db2.isConnected);  // true (same instance)
  //
  // db1.query("SELECT * FROM users");
}

/*
 * SOLUTION 6:
 *
 * class Database {
 *     static #instance = null;
 *
 *     constructor() {
 *         if (Database.#instance) {
 *             return Database.#instance;
 *         }
 *         this.isConnected = false;
 *         Database.#instance = this;
 *     }
 *
 *     static getInstance() {
 *         if (!Database.#instance) {
 *             new Database();
 *         }
 *         return Database.#instance;
 *     }
 *
 *     connect() {
 *         this.isConnected = true;
 *         console.log("Connected to database");
 *     }
 *
 *     query(sql) {
 *         if (!this.isConnected) {
 *             throw new Error("Not connected");
 *         }
 *         console.log(`Executing: ${sql}`);
 *         return [];
 *     }
 * }
 */

/**
 * EXERCISE 7: Registry Pattern
 * Difficulty: ⭐⭐⭐
 *
 * Create a ServiceRegistry class:
 * - Static register(name, service) - register a service
 * - Static get(name) - get a service by name
 * - Static has(name) - check if service exists
 * - Static unregister(name) - remove a service
 * - Static list() - list all service names
 */
function exercise7() {
  // YOUR CODE HERE
  // TEST CASES
  // ServiceRegistry.register("logger", { log: console.log });
  // ServiceRegistry.register("mailer", { send: () => "sent" });
  //
  // console.log(ServiceRegistry.has("logger"));   // true
  // console.log(ServiceRegistry.list());          // ["logger", "mailer"]
  //
  // const logger = ServiceRegistry.get("logger");
  // logger.log("Hello");  // "Hello"
  //
  // ServiceRegistry.unregister("mailer");
  // console.log(ServiceRegistry.list());          // ["logger"]
}

/*
 * SOLUTION 7:
 *
 * class ServiceRegistry {
 *     static #services = new Map();
 *
 *     static register(name, service) {
 *         if (this.#services.has(name)) {
 *             throw new Error(`Service "${name}" already registered`);
 *         }
 *         this.#services.set(name, service);
 *     }
 *
 *     static get(name) {
 *         if (!this.#services.has(name)) {
 *             throw new Error(`Service "${name}" not found`);
 *         }
 *         return this.#services.get(name);
 *     }
 *
 *     static has(name) {
 *         return this.#services.has(name);
 *     }
 *
 *     static unregister(name) {
 *         return this.#services.delete(name);
 *     }
 *
 *     static list() {
 *         return [...this.#services.keys()];
 *     }
 * }
 */

/**
 * EXERCISE 8: Static Initialization Block
 * Difficulty: ⭐⭐⭐
 *
 * Create an Environment class:
 * - Use static initialization block to detect environment
 * - Static properties: isDevelopment, isProduction, isTest
 * - Static get(key) to retrieve environment variables
 * - Static getAll() to get all environment info
 */
function exercise8() {
  // YOUR CODE HERE
  // TEST CASES
  // console.log(Environment.isDevelopment); // true or false
  // console.log(Environment.isProduction);  // true or false
  // console.log(Environment.name);          // "development", "production", or "test"
  // console.log(Environment.getAll());      // { name, isDevelopment, isProduction, isTest }
}

/*
 * SOLUTION 8:
 *
 * class Environment {
 *     static name;
 *     static isDevelopment;
 *     static isProduction;
 *     static isTest;
 *
 *     static {
 *         // Detect environment
 *         const env = process.env.NODE_ENV || "development";
 *
 *         this.name = env;
 *         this.isDevelopment = env === "development";
 *         this.isProduction = env === "production";
 *         this.isTest = env === "test";
 *     }
 *
 *     static get(key) {
 *         return process.env[key];
 *     }
 *
 *     static getAll() {
 *         return {
 *             name: this.name,
 *             isDevelopment: this.isDevelopment,
 *             isProduction: this.isProduction,
 *             isTest: this.isTest
 *         };
 *     }
 * }
 */

/**
 * EXERCISE 9: Cache with Expiry
 * Difficulty: ⭐⭐⭐
 *
 * Create a Cache class with static methods:
 * - set(key, value, ttl) - ttl in milliseconds
 * - get(key) - returns undefined if expired
 * - has(key) - check if key exists and not expired
 * - clear() - clear all entries
 */
function exercise9() {
  // YOUR CODE HERE
  // TEST CASES (use setTimeout to verify expiry)
  // Cache.set("key1", "value1", 100);  // Expires in 100ms
  // Cache.set("key2", "value2", 5000); // Expires in 5s
  //
  // console.log(Cache.get("key1"));    // "value1"
  // console.log(Cache.has("key1"));    // true
  //
  // setTimeout(() => {
  //     console.log(Cache.get("key1")); // undefined (expired)
  //     console.log(Cache.has("key2")); // true (still valid)
  // }, 200);
}

/*
 * SOLUTION 9:
 *
 * class Cache {
 *     static #data = new Map();
 *
 *     static set(key, value, ttl) {
 *         const expiry = ttl ? Date.now() + ttl : null;
 *         this.#data.set(key, { value, expiry });
 *     }
 *
 *     static get(key) {
 *         const entry = this.#data.get(key);
 *         if (!entry) return undefined;
 *
 *         if (entry.expiry && Date.now() > entry.expiry) {
 *             this.#data.delete(key);
 *             return undefined;
 *         }
 *
 *         return entry.value;
 *     }
 *
 *     static has(key) {
 *         return this.get(key) !== undefined;
 *     }
 *
 *     static clear() {
 *         this.#data.clear();
 *     }
 * }
 */

/**
 * EXERCISE 10: ID Generator Factory
 * Difficulty: ⭐⭐⭐
 *
 * Create an IDFactory class:
 * - Static createGenerator(prefix) returns a generator function
 * - Each generator maintains its own counter
 * - Static uuid() generates a random UUID
 * - Static timestamp() generates timestamp-based ID
 */
function exercise10() {
  // YOUR CODE HERE
  // TEST CASES
  // const userIdGen = IDFactory.createGenerator("USER");
  // const orderIdGen = IDFactory.createGenerator("ORDER");
  //
  // console.log(userIdGen());   // "USER_1"
  // console.log(userIdGen());   // "USER_2"
  // console.log(orderIdGen());  // "ORDER_1"
  // console.log(userIdGen());   // "USER_3"
  //
  // console.log(IDFactory.uuid());      // "xxxxxxxx-xxxx-..."
  // console.log(IDFactory.timestamp()); // "1234567890123"
}

/*
 * SOLUTION 10:
 *
 * class IDFactory {
 *     static createGenerator(prefix) {
 *         let count = 0;
 *         return () => `${prefix}_${++count}`;
 *     }
 *
 *     static uuid() {
 *         return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
 *             const r = Math.random() * 16 | 0;
 *             const v = c === 'x' ? r : (r & 0x3 | 0x8);
 *             return v.toString(16);
 *         });
 *     }
 *
 *     static timestamp() {
 *         return Date.now().toString();
 *     }
 * }
 */

/**
 * EXERCISE 11: Event Emitter
 * Difficulty: ⭐⭐⭐⭐
 *
 * Create a static EventBus class:
 * - on(event, callback) - subscribe (returns unsubscribe fn)
 * - emit(event, ...args) - trigger event
 * - once(event, callback) - subscribe for one-time
 * - off(event, callback) - unsubscribe
 * - clear(event?) - clear one or all events
 */
function exercise11() {
  // YOUR CODE HERE
  // TEST CASES
  // const unsub = EventBus.on("message", (msg) => console.log("Received:", msg));
  //
  // EventBus.emit("message", "Hello");  // "Received: Hello"
  //
  // unsub();  // Unsubscribe
  // EventBus.emit("message", "World");  // Nothing happens
  //
  // EventBus.once("connect", () => console.log("Connected!"));
  // EventBus.emit("connect");  // "Connected!"
  // EventBus.emit("connect");  // Nothing (once)
}

/*
 * SOLUTION 11:
 *
 * class EventBus {
 *     static #events = new Map();
 *
 *     static on(event, callback) {
 *         if (!this.#events.has(event)) {
 *             this.#events.set(event, new Set());
 *         }
 *         this.#events.get(event).add(callback);
 *         return () => this.off(event, callback);
 *     }
 *
 *     static emit(event, ...args) {
 *         const callbacks = this.#events.get(event);
 *         if (callbacks) {
 *             for (const callback of callbacks) {
 *                 callback(...args);
 *             }
 *         }
 *     }
 *
 *     static once(event, callback) {
 *         const wrapper = (...args) => {
 *             callback(...args);
 *             this.off(event, wrapper);
 *         };
 *         return this.on(event, wrapper);
 *     }
 *
 *     static off(event, callback) {
 *         const callbacks = this.#events.get(event);
 *         if (callbacks) {
 *             callbacks.delete(callback);
 *         }
 *     }
 *
 *     static clear(event) {
 *         if (event) {
 *             this.#events.delete(event);
 *         } else {
 *             this.#events.clear();
 *         }
 *     }
 * }
 */

/**
 * EXERCISE 12: Object Pool
 * Difficulty: ⭐⭐⭐⭐
 *
 * Create a Pool class:
 * - Constructor takes factory function and max size
 * - acquire() - get object from pool or create new
 * - release(obj) - return object to pool
 * - Static stats() - return pool statistics
 */
function exercise12() {
  // YOUR CODE HERE
  // TEST CASES
  // class Connection {
  //     constructor() { this.id = Math.random(); }
  //     query(sql) { console.log(`[${this.id}] ${sql}`); }
  // }
  //
  // const pool = new Pool(() => new Connection(), 3);
  //
  // const conn1 = pool.acquire();
  // const conn2 = pool.acquire();
  //
  // conn1.query("SELECT 1");
  //
  // pool.release(conn1);
  //
  // const conn3 = pool.acquire();  // Returns conn1 (reused)
  // console.log(conn1 === conn3);  // true
  //
  // console.log(pool.stats());  // { total: 2, available: 0, inUse: 2 }
}

/*
 * SOLUTION 12:
 *
 * class Pool {
 *     #factory;
 *     #maxSize;
 *     #available = [];
 *     #inUse = new Set();
 *
 *     constructor(factory, maxSize = 10) {
 *         this.#factory = factory;
 *         this.#maxSize = maxSize;
 *     }
 *
 *     acquire() {
 *         let obj;
 *
 *         if (this.#available.length > 0) {
 *             obj = this.#available.pop();
 *         } else if (this.#inUse.size < this.#maxSize) {
 *             obj = this.#factory();
 *         } else {
 *             throw new Error("Pool exhausted");
 *         }
 *
 *         this.#inUse.add(obj);
 *         return obj;
 *     }
 *
 *     release(obj) {
 *         if (this.#inUse.has(obj)) {
 *             this.#inUse.delete(obj);
 *             this.#available.push(obj);
 *         }
 *     }
 *
 *     stats() {
 *         return {
 *             total: this.#available.length + this.#inUse.size,
 *             available: this.#available.length,
 *             inUse: this.#inUse.size
 *         };
 *     }
 * }
 */

/**
 * EXERCISE 13: Static Inheritance
 * Difficulty: ⭐⭐⭐⭐
 *
 * Create:
 * - Model base class with static tableName and find(id)
 * - User class extending Model with own tableName
 * - Each class should use its own table name in find()
 * - Demonstrate static inheritance behavior
 */
function exercise13() {
  // YOUR CODE HERE
  // TEST CASES
  // console.log(Model.tableName);     // "models"
  // console.log(User.tableName);      // "users"
  //
  // Model.find(1);  // "SELECT * FROM models WHERE id = 1"
  // User.find(1);   // "SELECT * FROM users WHERE id = 1"
}

/*
 * SOLUTION 13:
 *
 * class Model {
 *     static tableName = "models";
 *
 *     static find(id) {
 *         console.log(`SELECT * FROM ${this.tableName} WHERE id = ${id}`);
 *         return { id };
 *     }
 *
 *     static all() {
 *         console.log(`SELECT * FROM ${this.tableName}`);
 *         return [];
 *     }
 * }
 *
 * class User extends Model {
 *     static tableName = "users";
 *
 *     constructor(name, email) {
 *         super();
 *         this.name = name;
 *         this.email = email;
 *     }
 *
 *     static findByEmail(email) {
 *         console.log(`SELECT * FROM ${this.tableName} WHERE email = '${email}'`);
 *         return null;
 *     }
 * }
 */

/**
 * EXERCISE 14: Metrics Collector
 * Difficulty: ⭐⭐⭐⭐
 *
 * Create a Metrics class:
 * - Static increment(name) - increment a counter
 * - Static timing(name, ms) - record a timing
 * - Static gauge(name, value) - set a gauge value
 * - Static getMetrics() - return all metrics
 * - Static reset() - clear all metrics
 */
function exercise14() {
  // YOUR CODE HERE
  // TEST CASES
  // Metrics.increment("requests");
  // Metrics.increment("requests");
  // Metrics.increment("errors");
  //
  // Metrics.timing("response_time", 150);
  // Metrics.timing("response_time", 200);
  //
  // Metrics.gauge("memory_usage", 75.5);
  //
  // console.log(Metrics.getMetrics());
  // // {
  // //   counters: { requests: 2, errors: 1 },
  // //   timings: { response_time: { count: 2, total: 350, avg: 175 } },
  // //   gauges: { memory_usage: 75.5 }
  // // }
}

/*
 * SOLUTION 14:
 *
 * class Metrics {
 *     static #counters = new Map();
 *     static #timings = new Map();
 *     static #gauges = new Map();
 *
 *     static increment(name, value = 1) {
 *         const current = this.#counters.get(name) || 0;
 *         this.#counters.set(name, current + value);
 *     }
 *
 *     static timing(name, ms) {
 *         if (!this.#timings.has(name)) {
 *             this.#timings.set(name, { count: 0, total: 0 });
 *         }
 *         const timing = this.#timings.get(name);
 *         timing.count++;
 *         timing.total += ms;
 *     }
 *
 *     static gauge(name, value) {
 *         this.#gauges.set(name, value);
 *     }
 *
 *     static getMetrics() {
 *         const counters = Object.fromEntries(this.#counters);
 *         const timings = {};
 *         for (const [name, data] of this.#timings) {
 *             timings[name] = {
 *                 ...data,
 *                 avg: data.total / data.count
 *             };
 *         }
 *         const gauges = Object.fromEntries(this.#gauges);
 *
 *         return { counters, timings, gauges };
 *     }
 *
 *     static reset() {
 *         this.#counters.clear();
 *         this.#timings.clear();
 *         this.#gauges.clear();
 *     }
 * }
 */

/**
 * EXERCISE 15: Complete API Client
 * Difficulty: ⭐⭐⭐⭐⭐
 *
 * Create an ApiClient class:
 * - Static baseUrl, headers, interceptors
 * - Static configure({ baseUrl, headers })
 * - Static addInterceptor(fn) for request modification
 * - Static async get(endpoint), post(endpoint, data)
 * - Request caching with TTL
 * - Request/response logging
 */
function exercise15() {
  // YOUR CODE HERE
  // TEST CASES
  // ApiClient.configure({
  //     baseUrl: "https://api.example.com",
  //     headers: { "Content-Type": "application/json" }
  // });
  //
  // ApiClient.addInterceptor((config) => {
  //     config.headers["Authorization"] = "Bearer token123";
  //     return config;
  // });
  //
  // await ApiClient.get("/users");        // GET https://api.example.com/users
  // await ApiClient.post("/users", { name: "Alice" });  // POST with data
  //
  // console.log(ApiClient.stats);  // { requests: 2, cached: 0 }
}

/*
 * SOLUTION 15:
 *
 * class ApiClient {
 *     static #baseUrl = "";
 *     static #headers = {};
 *     static #interceptors = [];
 *     static #cache = new Map();
 *     static #cacheTTL = 60000;  // 1 minute
 *     static #requestCount = 0;
 *     static #cacheHits = 0;
 *
 *     static configure({ baseUrl, headers, cacheTTL }) {
 *         if (baseUrl) this.#baseUrl = baseUrl;
 *         if (headers) this.#headers = { ...this.#headers, ...headers };
 *         if (cacheTTL) this.#cacheTTL = cacheTTL;
 *     }
 *
 *     static addInterceptor(fn) {
 *         this.#interceptors.push(fn);
 *         return () => {
 *             const index = this.#interceptors.indexOf(fn);
 *             if (index > -1) this.#interceptors.splice(index, 1);
 *         };
 *     }
 *
 *     static async #request(method, endpoint, data = null) {
 *         this.#requestCount++;
 *
 *         let config = {
 *             method,
 *             url: `${this.#baseUrl}${endpoint}`,
 *             headers: { ...this.#headers },
 *             data
 *         };
 *
 *         // Apply interceptors
 *         for (const interceptor of this.#interceptors) {
 *             config = interceptor(config);
 *         }
 *
 *         console.log(`[${method}] ${config.url}`);
 *
 *         // Simulated response
 *         return { status: 200, data: { success: true } };
 *     }
 *
 *     static async get(endpoint, useCache = true) {
 *         const cacheKey = `GET:${endpoint}`;
 *         const cached = this.#cache.get(cacheKey);
 *
 *         if (useCache && cached && Date.now() < cached.expiry) {
 *             this.#cacheHits++;
 *             console.log(`[CACHE] ${endpoint}`);
 *             return cached.data;
 *         }
 *
 *         const result = await this.#request("GET", endpoint);
 *
 *         if (useCache) {
 *             this.#cache.set(cacheKey, {
 *                 data: result,
 *                 expiry: Date.now() + this.#cacheTTL
 *             });
 *         }
 *
 *         return result;
 *     }
 *
 *     static async post(endpoint, data) {
 *         return this.#request("POST", endpoint, data);
 *     }
 *
 *     static async put(endpoint, data) {
 *         return this.#request("PUT", endpoint, data);
 *     }
 *
 *     static async delete(endpoint) {
 *         return this.#request("DELETE", endpoint);
 *     }
 *
 *     static get stats() {
 *         return {
 *             requests: this.#requestCount,
 *             cached: this.#cacheHits
 *         };
 *     }
 *
 *     static clearCache() {
 *         this.#cache.clear();
 *     }
 * }
 */

// Run exercises
console.log('=== Static Members 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