javascript
exercises
exercises.js⚡javascript
/**
* ============================================
* 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!');