Static Functions & Variables
Introduction
Static members (properties and methods) belong to the class itself, not to individual instances. They're like "class-level" functionality that's shared across all objects created from that class.
Think of static members as:
- Utilities - Helper functions that don't need object state
- Constants - Values that are the same for all instances
- Counters - Tracking information across all instances
- Factory methods - Creating instances in special ways
The key difference:
- Instance members: Each object has its own copy (e.g., each person has their own name)
- Static members: One copy shared by all (e.g., all humans share the same number of chromosomes)
Why Static Members Matter
Without static (repetitive):
function add(a: number, b: number): number {
return a + b;
}
function subtract(a: number, b: number): number {
return a - b;
}
// Random utility functions floating around...
With static (organized):
class MathUtils {
static add(a: number, b: number): number {
return a + b;
}
static subtract(a: number, b: number): number {
return a - b;
}
}
// All math utilities organized in one place!
MathUtils.add(5, 3);
Instance vs Static
class Person {
// Instance property - each person has their own name
name: string;
// Static property - shared by all Person instances
static species: string = "Homo sapiens";
constructor(name: string) {
this.name = name;
}
// Instance method - operates on specific person
introduce(): string {
return `Hi, I'm ${this.name}`;
}
// Static method - doesn't need a specific person
static getSpecies(): string {
return Person.species;
}
}
let alice = new Person("Alice");
let bob = new Person("Bob");
// Instance members - accessed through objects
console.log(alice.name); // "Alice"
console.log(bob.name); // "Bob"
console.log(alice.introduce()); // "Hi, I'm Alice"
// Static members - accessed through the class
console.log(Person.species); // "Homo sapiens"
console.log(Person.getSpecies()); // "Homo sapiens"
// Can't access static members on instances
// console.log(alice.species); // ❌ Error: Property 'species' does not exist on type 'Person'
Static Properties
Static properties are variables that belong to the class, not instances:
Basic Static Properties
class Config {
static appName: string = "MyApp";
static version: string = "1.0.0";
static maxUsers: number = 1000;
}
// Access without creating an instance
console.log(Config.appName); // "MyApp"
console.log(Config.version); // "1.0.0"
console.log(Config.maxUsers); // 1000
// Can modify static properties
Config.version = "1.0.1";
console.log(Config.version); // "1.0.1"
Constants with Static
class MathConstants {
static readonly PI: number = 3.14159;
static readonly E: number = 2.71828;
static readonly GOLDEN_RATIO: number = 1.61803;
}
console.log(MathConstants.PI); // 3.14159
// Can't modify readonly static properties
// MathConstants.PI = 3.14; // ❌ Error: Cannot assign to 'PI' because it is a read-only property
Instance Counters
A common use case is counting instances:
class User {
private static userCount: number = 0;
readonly id: number;
constructor(public name: string) {
User.userCount++;
this.id = User.userCount;
}
static getTotalUsers(): number {
return User.userCount;
}
static resetCount(): void {
User.userCount = 0;
}
}
console.log(User.getTotalUsers()); // 0
let user1 = new User("Alice");
let user2 = new User("Bob");
let user3 = new User("Charlie");
console.log(User.getTotalUsers()); // 3
console.log(user1.id); // 1
console.log(user2.id); // 2
console.log(user3.id); // 3
Static Methods
Static methods are functions that belong to the class:
Utility Methods
class StringUtils {
static capitalize(str: string): string {
if (str.length === 0) return str;
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
static reverse(str: string): string {
return str.split("").reverse().join("");
}
static isPalindrome(str: string): boolean {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, "");
return cleaned === StringUtils.reverse(cleaned).toLowerCase();
}
}
console.log(StringUtils.capitalize("hello")); // "Hello"
console.log(StringUtils.reverse("hello")); // "olleh"
console.log(StringUtils.isPalindrome("racecar")); // true
Validation Methods
class Validator {
static isEmail(email: string): boolean {
return email.includes("@") && email.includes(".");
}
static isValidPassword(password: string): boolean {
return password.length >= 8;
}
static isNonEmpty(str: string): boolean {
return str.trim().length > 0;
}
static isPositiveNumber(num: number): boolean {
return num > 0;
}
}
console.log(Validator.isEmail("user@example.com")); // true
console.log(Validator.isEmail("invalid")); // false
console.log(Validator.isValidPassword("pass123")); // false
console.log(Validator.isValidPassword("password123")); // true
Conversion Methods
class TemperatureConverter {
static celsiusToFahrenheit(celsius: number): number {
return (celsius * 9) / 5 + 32;
}
static fahrenheitToCelsius(fahrenheit: number): number {
return ((fahrenheit - 32) * 5) / 9;
}
static celsiusToKelvin(celsius: number): number {
return celsius + 273.15;
}
static kelvinToCelsius(kelvin: number): number {
return kelvin - 273.15;
}
}
console.log(TemperatureConverter.celsiusToFahrenheit(0)); // 32
console.log(TemperatureConverter.celsiusToFahrenheit(100)); // 212
console.log(TemperatureConverter.fahrenheitToCelsius(32)); // 0
console.log(TemperatureConverter.celsiusToKelvin(0)); // 273.15
Static vs Instance: When to Use Each
Use Static When:
- The method doesn't need object state
class MathHelper {
// ✅ Good - doesn't need any object state
static add(a: number, b: number): number {
return a + b;
}
}
- Creating utility functions
class DateUtils {
static formatDate(date: Date): string {
return date.toISOString().split("T")[0];
}
}
- Tracking class-wide information
class BankAccount {
private static totalAccounts: number = 0;
constructor() {
BankAccount.totalAccounts++;
}
static getTotalAccounts(): number {
return BankAccount.totalAccounts;
}
}
Use Instance When:
- The method needs object state
class BankAccount {
private balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
// ✅ Good - needs the specific account's balance
getBalance(): number {
return this.balance;
}
}
- The behavior varies per object
class Person {
constructor(public name: string, public age: number) {}
// ✅ Good - each person introduces themselves differently
introduce(): string {
return `Hi, I'm ${this.name}, ${this.age} years old`;
}
}
Factory Methods
Static methods are often used as factory methods to create instances:
class User {
private constructor(
public id: number,
public name: string,
public email: string
) {}
// Factory method - creates a regular user
static createRegular(name: string, email: string): User {
const id = Math.floor(Math.random() * 10000);
return new User(id, name, email);
}
// Factory method - creates an admin user
static createAdmin(name: string, email: string): User {
const id = 1; // Admin always has ID 1
return new User(id, name, email.toLowerCase());
}
// Factory method - creates from JSON
static fromJSON(json: string): User {
const data = JSON.parse(json);
return new User(data.id, data.name, data.email);
}
}
let regularUser = User.createRegular("Alice", "alice@email.com");
let adminUser = User.createAdmin("Admin", "ADMIN@email.com");
let jsonUser = User.fromJSON('{"id":5,"name":"Bob","email":"bob@email.com"}');
console.log(regularUser);
console.log(adminUser);
console.log(jsonUser);
Static Blocks
Initialize static properties with complex logic:
class Config {
static environment: string;
static apiUrl: string;
// Static initialization block
static {
const isDevelopment = process.env.NODE_ENV === "development";
Config.environment = isDevelopment ? "dev" : "prod";
Config.apiUrl = isDevelopment
? "http://localhost:3000"
: "https://api.production.com";
}
}
console.log(Config.environment); // "dev" or "prod"
console.log(Config.apiUrl);
Combining Static and Instance
Static and instance members work together:
class BankAccount {
private static accountCount: number = 0;
private static totalMoney: number = 0;
readonly id: number;
private balance: number;
constructor(public owner: string, initialBalance: number) {
BankAccount.accountCount++;
this.id = BankAccount.accountCount;
this.balance = initialBalance;
BankAccount.totalMoney += initialBalance;
}
// Instance methods
deposit(amount: number): void {
this.balance += amount;
BankAccount.totalMoney += amount;
}
withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error("Insufficient funds");
}
this.balance -= amount;
BankAccount.totalMoney -= amount;
}
getBalance(): number {
return this.balance;
}
// Static methods
static getTotalAccounts(): number {
return BankAccount.accountCount;
}
static getTotalMoney(): number {
return BankAccount.totalMoney;
}
static getAverageBalance(): number {
if (BankAccount.accountCount === 0) return 0;
return BankAccount.totalMoney / BankAccount.accountCount;
}
}
let acc1 = new BankAccount("Alice", 1000);
let acc2 = new BankAccount("Bob", 500);
let acc3 = new BankAccount("Charlie", 2000);
acc1.deposit(500);
acc2.withdraw(100);
console.log(`Total accounts: ${BankAccount.getTotalAccounts()}`); // 3
console.log(`Total money: ${BankAccount.getTotalMoney()}`); // 3900
console.log(`Average balance: ${BankAccount.getAverageBalance()}`); // 1300
Accessing Static Members
From Outside the Class
class Calculator {
static PI: number = 3.14159;
static circleArea(radius: number): number {
return Calculator.PI * radius ** 2;
}
}
// ✅ Correct - access through class name
console.log(Calculator.PI);
console.log(Calculator.circleArea(5));
// ❌ Wrong - can't access through instance
let calc = new Calculator();
// console.log(calc.PI); // Error
From Within the Class
class Example {
static value: number = 42;
static method1(): void {
// ✅ Can access other static members
console.log(Example.value);
Example.method2();
}
static method2(): void {
console.log("Method 2 called");
}
instanceMethod(): void {
// ✅ Can access static members from instance methods
console.log(Example.value);
Example.method1();
}
}
Concepts
Quick Reference
// Static property
class Config {
static appName: string = "MyApp";
}
// Static method
class Utils {
static add(a: number, b: number): number {
return a + b;
}
}
// Instance counter
class User {
private static count: number = 0;
constructor() {
User.count++;
}
static getCount(): number {
return User.count;
}
}
// Static readonly (constant)
class Constants {
static readonly PI: number = 3.14159;
}
// Factory method
class Product {
static create(name: string): Product {
return new Product(name);
}
}
// Access static members
ClassName.staticProperty;
ClassName.staticMethod();
// Can't access on instances
let instance = new ClassName();
// instance.staticProperty; // ❌ Error
Examples
// ID Generator utility
class IDGenerator {
private static currentId: number = 0;
static generateId(): number {
return ++IDGenerator.currentId;
}
static resetCounter(): void {
IDGenerator.currentId = 0;
}
}
// Math utilities
class MathHelper {
static readonly PI: number = Math.PI;
static readonly E: number = Math.E;
static add(a: number, b: number): number {
return a + b;
}
static max(...numbers: number[]): number {
return Math.max(...numbers);
}
static circleArea(radius: number): number {
return MathHelper.PI * radius ** 2;
}
}
// Logger with history
class Logger {
private static logs: string[] = [];
static log(message: string): void {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}`;
Logger.logs.push(logEntry);
console.log(logEntry);
}
static getLogs(): string[] {
return [...Logger.logs];
}
static clearLogs(): void {
Logger.logs = [];
}
}
Challenges
1. MathHelper with Basic Operations
- Create a
MathHelperclass with static methods for add, subtract, and multiply. - Add proper type annotations.
- Test all three methods.
💡 Show Answer
class MathHelper {
static add(a: number, b: number): number {
return a + b;
}
static subtract(a: number, b: number): number {
return a - b;
}
static multiply(a: number, b: number): number {
return a * b;
}
static divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
static power(base: number, exponent: number): number {
return Math.pow(base, exponent);
}
static squareRoot(n: number): number {
if (n < 0) {
throw new Error("Cannot calculate square root of negative number");
}
return Math.sqrt(n);
}
}
// Test the methods
console.log(MathHelper.add(5, 3)); // 8
console.log(MathHelper.subtract(10, 4)); // 6
console.log(MathHelper.multiply(7, 6)); // 42
console.log(MathHelper.divide(20, 4)); // 5
console.log(MathHelper.power(2, 3)); // 8
console.log(MathHelper.squareRoot(16)); // 4
// Chain operations
let result = MathHelper.add(
MathHelper.multiply(3, 4),
MathHelper.subtract(10, 5)
);
console.log(result); // 17 (12 + 5)
2. Add PI Constant
- Add a static property
pito MathHelper. - Create a static method to calculate circle circumference.
- Make pi readonly.
💡 Show Answer
class MathHelper {
static readonly PI: number = 3.14159;
static readonly E: number = 2.71828;
static circleCircumference(radius: number): number {
return 2 * MathHelper.PI * radius;
}
static circleArea(radius: number): number {
return MathHelper.PI * radius ** 2;
}
static sphereVolume(radius: number): number {
return (4 / 3) * MathHelper.PI * radius ** 3;
}
static sphereSurfaceArea(radius: number): number {
return 4 * MathHelper.PI * radius ** 2;
}
}
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.circleCircumference(5)); // 31.4159
console.log(MathHelper.circleArea(5)); // 78.53975
console.log(MathHelper.sphereVolume(3)); // 113.09724
console.log(MathHelper.sphereSurfaceArea(3)); // 113.09724
// Can't modify readonly
// MathHelper.PI = 3.14; // ❌ Error: Cannot assign to 'PI' because it is a read-only property
// Using built-in Math.PI for more precision
class PreciseMath {
static readonly PI: number = Math.PI;
static circleArea(radius: number): number {
return PreciseMath.PI * radius ** 2;
}
}
console.log(PreciseMath.PI); // 3.141592653589793
3. Counter Class
- Create a
Counterclass with a staticcountproperty. - Increment the count in the constructor.
- Add a static method to get the count.
💡 Show Answer
class Counter {
private static count: number = 0;
constructor() {
Counter.count++;
}
static getCount(): number {
return Counter.count;
}
static resetCount(): void {
Counter.count = 0;
}
static incrementBy(amount: number): void {
Counter.count += amount;
}
}
console.log(Counter.getCount()); // 0
let c1 = new Counter();
console.log(Counter.getCount()); // 1
let c2 = new Counter();
let c3 = new Counter();
console.log(Counter.getCount()); // 3
Counter.incrementBy(10);
console.log(Counter.getCount()); // 13
Counter.resetCount();
console.log(Counter.getCount()); // 0
// Named counter
class NamedCounter {
private static count: number = 0;
constructor(public name: string) {
NamedCounter.count++;
console.log(`Counter "${name}" created. Total: ${NamedCounter.count}`);
}
static getCount(): number {
return NamedCounter.count;
}
}
let nc1 = new NamedCounter("First"); // "Counter "First" created. Total: 1"
let nc2 = new NamedCounter("Second"); // "Counter "Second" created. Total: 2"
4. Config Class with App Settings
- Create a
Configclass with static properties for app settings. - Include
appName,version, andmaxUsers. - Access them without creating an instance.
💡 Show Answer
class Config {
static readonly appName: string = "MyApp";
static readonly version: string = "1.0.0";
static maxUsers: number = 1000;
static debugMode: boolean = false;
static apiUrl: string = "https://api.example.com";
static getInfo(): string {
return `${Config.appName} v${Config.version}`;
}
static enableDebugMode(): void {
Config.debugMode = true;
console.log("Debug mode enabled");
}
static disableDebugMode(): void {
Config.debugMode = false;
console.log("Debug mode disabled");
}
static setMaxUsers(max: number): void {
if (max < 1) {
throw new Error("Max users must be at least 1");
}
Config.maxUsers = max;
}
}
// Access without creating an instance
console.log(Config.appName); // "MyApp"
console.log(Config.version); // "1.0.0"
console.log(Config.maxUsers); // 1000
console.log(Config.getInfo()); // "MyApp v1.0.0"
Config.enableDebugMode();
console.log(Config.debugMode); // true
Config.setMaxUsers(2000);
console.log(Config.maxUsers); // 2000
// Environment-specific config
class EnvironmentConfig {
private static environment: "development" | "production" = "development";
static readonly development = {
apiUrl: "http://localhost:3000",
debugMode: true,
logLevel: "verbose"
};
static readonly production = {
apiUrl: "https://api.production.com",
debugMode: false,
logLevel: "error"
};
static get current() {
return EnvironmentConfig.environment === "development"
? EnvironmentConfig.development
: EnvironmentConfig.production;
}
static setEnvironment(env: "development" | "production"): void {
EnvironmentConfig.environment = env;
}
}
console.log(EnvironmentConfig.current.apiUrl); // "http://localhost:3000"
5. User Class with Total Users Tracking
- Create a
Userclass that tracks total users with a static property. - Increment it in the constructor.
- Add a static method to get the total.
💡 Show Answer
class User {
private static totalUsers: number = 0;
readonly id: number;
constructor(public name: string, public email: string) {
User.totalUsers++;
this.id = User.totalUsers;
}
static getTotalUsers(): number {
return User.totalUsers;
}
static resetCount(): void {
User.totalUsers = 0;
}
getInfo(): string {
return `User #${this.id}: ${this.name} (${this.email})`;
}
}
console.log(User.getTotalUsers()); // 0
let user1 = new User("Alice", "alice@email.com");
let user2 = new User("Bob", "bob@email.com");
let user3 = new User("Charlie", "charlie@email.com");
console.log(User.getTotalUsers()); // 3
console.log(user1.getInfo()); // "User #1: Alice (alice@email.com)"
console.log(user2.getInfo()); // "User #2: Bob (bob@email.com)"
console.log(user3.getInfo()); // "User #3: Charlie (charlie@email.com)"
// Enhanced with user list
class UserRegistry {
private static users: UserRegistry[] = [];
readonly id: number;
constructor(public name: string, public email: string) {
this.id = UserRegistry.users.length + 1;
UserRegistry.users.push(this);
}
static getTotalUsers(): number {
return UserRegistry.users.length;
}
static getAllUsers(): UserRegistry[] {
return [...UserRegistry.users];
}
static findById(id: number): UserRegistry | undefined {
return UserRegistry.users.find(user => user.id === id);
}
static findByEmail(email: string): UserRegistry | undefined {
return UserRegistry.users.find(user => user.email === email);
}
}
let u1 = new UserRegistry("Alice", "alice@email.com");
let u2 = new UserRegistry("Bob", "bob@email.com");
console.log(UserRegistry.getTotalUsers()); // 2
console.log(UserRegistry.findById(1)); // Alice
console.log(UserRegistry.findByEmail("bob@email.com")); // Bob
6. Validator Class with Static Methods
- Create a
Validatorclass with static validation methods. - Include
isEmail()andisNonEmpty(). - Add more validators like
isValidPassword().
💡 Show Answer
class Validator {
static isEmail(email: string): boolean {
return email.includes("@") && email.includes(".");
}
static isNonEmpty(str: string): boolean {
return str.trim().length > 0;
}
static isValidPassword(password: string): boolean {
return password.length >= 8;
}
static isPositiveNumber(num: number): boolean {
return num > 0;
}
static isValidURL(url: string): boolean {
return url.startsWith("http://") || url.startsWith("https://");
}
static isAlphanumeric(str: string): boolean {
return /^[a-zA-Z0-9]+$/.test(str);
}
static isValidPhone(phone: string): boolean {
// Simple check - at least 10 digits
const digits = phone.replace(/\D/g, "");
return digits.length >= 10;
}
static isInRange(num: number, min: number, max: number): boolean {
return num >= min && num <= max;
}
static hasMinLength(str: string, minLength: number): boolean {
return str.length >= minLength;
}
static hasMaxLength(str: string, maxLength: number): boolean {
return str.length <= maxLength;
}
}
// Test validators
console.log(Validator.isEmail("user@example.com")); // true
console.log(Validator.isEmail("invalid")); // false
console.log(Validator.isNonEmpty("hello")); // true
console.log(Validator.isNonEmpty(" ")); // false
console.log(Validator.isValidPassword("pass123")); // false
console.log(Validator.isValidPassword("password123")); // true
console.log(Validator.isPositiveNumber(5)); // true
console.log(Validator.isPositiveNumber(-5)); // false
console.log(Validator.isValidURL("https://google.com")); // true
console.log(Validator.isValidURL("google.com")); // false
// Form validation example
interface FormData {
email: string;
password: string;
age: number;
}
function validateForm(data: FormData): string[] {
const errors: string[] = [];
if (!Validator.isEmail(data.email)) {
errors.push("Invalid email address");
}
if (!Validator.isValidPassword(data.password)) {
errors.push("Password must be at least 8 characters");
}
if (!Validator.isInRange(data.age, 18, 120)) {
errors.push("Age must be between 18 and 120");
}
return errors;
}
let form1 = { email: "user@example.com", password: "secure123", age: 25 };
let form2 = { email: "invalid", password: "short", age: 15 };
console.log(validateForm(form1)); // []
console.log(validateForm(form2)); // ["Invalid email address", "Password must be at least 8 characters", "Age must be between 18 and 120"]
7. BankAccount with Total Accounts Tracking
- Create a
BankAccountclass with staticaccountCount. - Track the total number of accounts created.
- Add a static method
getTotalAccounts().
💡 Show Answer
class BankAccount {
private static accountCount: number = 0;
private static totalDeposits: number = 0;
readonly accountNumber: number;
private balance: number;
constructor(public owner: string, initialBalance: number) {
BankAccount.accountCount++;
this.accountNumber = BankAccount.accountCount;
this.balance = initialBalance;
BankAccount.totalDeposits += initialBalance;
}
deposit(amount: number): void {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this.balance += amount;
BankAccount.totalDeposits += amount;
}
withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error("Insufficient funds");
}
this.balance -= amount;
}
getBalance(): number {
return this.balance;
}
static getTotalAccounts(): number {
return BankAccount.accountCount;
}
static getTotalDeposits(): number {
return BankAccount.totalDeposits;
}
static getAverageBalance(): number {
if (BankAccount.accountCount === 0) return 0;
return BankAccount.totalDeposits / BankAccount.accountCount;
}
getAccountInfo(): string {
return `Account #${this.accountNumber} - ${this.owner}: $${this.balance}`;
}
}
console.log(BankAccount.getTotalAccounts()); // 0
let acc1 = new BankAccount("Alice", 1000);
let acc2 = new BankAccount("Bob", 500);
let acc3 = new BankAccount("Charlie", 2000);
console.log(BankAccount.getTotalAccounts()); // 3
console.log(BankAccount.getTotalDeposits()); // 3500
console.log(BankAccount.getAverageBalance()); // 1166.67
acc1.deposit(500);
acc2.withdraw(200);
console.log(BankAccount.getTotalDeposits()); // 4000
console.log(acc1.getAccountInfo()); // "Account #1 - Alice: $1500"
console.log(acc2.getAccountInfo()); // "Account #2 - Bob: $300"
// Enhanced with account registry
class BankAccountRegistry extends BankAccount {
private static accounts: BankAccountRegistry[] = [];
constructor(owner: string, initialBalance: number) {
super(owner, initialBalance);
BankAccountRegistry.accounts.push(this);
}
static findByOwner(owner: string): BankAccountRegistry | undefined {
return BankAccountRegistry.accounts.find(acc => acc.owner === owner);
}
static getAllAccounts(): BankAccountRegistry[] {
return [...BankAccountRegistry.accounts];
}
static getTotalBalanceAllAccounts(): number {
return BankAccountRegistry.accounts.reduce(
(sum, acc) => sum + acc.getBalance(),
0
);
}
}
8. Temperature Converter
- Create a
TemperatureConverterclass. - Add static methods for Celsius ↔ Fahrenheit conversion.
- Add Kelvin conversions too.
💡 Show Answer
class TemperatureConverter {
static celsiusToFahrenheit(celsius: number): number {
return (celsius * 9) / 5 + 32;
}
static fahrenheitToCelsius(fahrenheit: number): number {
return ((fahrenheit - 32) * 5) / 9;
}
static celsiusToKelvin(celsius: number): number {
return celsius + 273.15;
}
static kelvinToCelsius(kelvin: number): number {
return kelvin - 273.15;
}
static fahrenheitToKelvin(fahrenheit: number): number {
const celsius = TemperatureConverter.fahrenheitToCelsius(fahrenheit);
return TemperatureConverter.celsiusToKelvin(celsius);
}
static kelvinToFahrenheit(kelvin: number): number {
const celsius = TemperatureConverter.kelvinToCelsius(kelvin);
return TemperatureConverter.celsiusToFahrenheit(celsius);
}
static format(temp: number, unit: "C" | "F" | "K"): string {
return `${temp.toFixed(2)}°${unit}`;
}
}
// Test conversions
console.log(TemperatureConverter.celsiusToFahrenheit(0)); // 32
console.log(TemperatureConverter.celsiusToFahrenheit(100)); // 212
console.log(TemperatureConverter.fahrenheitToCelsius(32)); // 0
console.log(TemperatureConverter.fahrenheitToCelsius(212)); // 100
console.log(TemperatureConverter.celsiusToKelvin(0)); // 273.15
console.log(TemperatureConverter.kelvinToCelsius(273.15)); // 0
// Formatted output
console.log(TemperatureConverter.format(25, "C")); // "25.00°C"
console.log(TemperatureConverter.format(77, "F")); // "77.00°F"
// Conversion table
function printConversionTable(celsius: number): void {
const fahrenheit = TemperatureConverter.celsiusToFahrenheit(celsius);
const kelvin = TemperatureConverter.celsiusToKelvin(celsius);
console.log(`Temperature Conversions for ${celsius}°C:`);
console.log(` Celsius: ${TemperatureConverter.format(celsius, "C")}`);
console.log(` Fahrenheit: ${TemperatureConverter.format(fahrenheit, "F")}`);
console.log(` Kelvin: ${TemperatureConverter.format(kelvin, "K")}`);
}
printConversionTable(25);
printConversionTable(0);
printConversionTable(100);
// Weather report
class WeatherReport {
constructor(
public location: string,
public temperatureCelsius: number
) {}
getTemperature(unit: "C" | "F" | "K"): string {
let temp: number;
switch (unit) {
case "C":
temp = this.temperatureCelsius;
break;
case "F":
temp = TemperatureConverter.celsiusToFahrenheit(this.temperatureCelsius);
break;
case "K":
temp = TemperatureConverter.celsiusToKelvin(this.temperatureCelsius);
break;
}
return TemperatureConverter.format(temp, unit);
}
}
let weather = new WeatherReport("London", 18);
console.log(`${weather.location}: ${weather.getTemperature("C")}`);
console.log(`${weather.location}: ${weather.getTemperature("F")}`);
9. Random ID Generator
- Add a static method
randomId()to a utility class. - Generate random numbers between 1000 and 9999.
- Add methods for different ID formats.
💡 Show Answer
class IDGenerator {
static randomId(): number {
return Math.floor(Math.random() * 9000) + 1000;
}
static randomIdInRange(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
static randomHexId(length: number = 8): string {
let result = "";
const chars = "0123456789abcdef";
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
static randomAlphanumericId(length: number = 10): string {
let result = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
static uuid(): string {
// Simple UUID v4 implementation
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);
});
}
private static sequentialId: number = 0;
static nextSequentialId(): number {
return ++IDGenerator.sequentialId;
}
static resetSequentialId(): void {
IDGenerator.sequentialId = 0;
}
}
// Test random ID generation
console.log("Random IDs (1000-9999):");
for (let i = 0; i < 5; i++) {
console.log(IDGenerator.randomId());
}
console.log("\nRandom IDs in range (100-200):");
for (let i = 0; i < 5; i++) {
console.log(IDGenerator.randomIdInRange(100, 200));
}
console.log("\nHex IDs:");
for (let i = 0; i < 5; i++) {
console.log(IDGenerator.randomHexId());
}
console.log("\nAlphanumeric IDs:");
for (let i = 0; i < 5; i++) {
console.log(IDGenerator.randomAlphanumericId());
}
console.log("\nUUIDs:");
for (let i = 0; i < 3; i++) {
console.log(IDGenerator.uuid());
}
console.log("\nSequential IDs:");
for (let i = 0; i < 5; i++) {
console.log(IDGenerator.nextSequentialId());
}
// Use in a class
class Product {
readonly id: string;
constructor(public name: string, public price: number) {
this.id = IDGenerator.uuid();
}
getInfo(): string {
return `Product ${this.id}: ${this.name} ($${this.price})`;
}
}
let product1 = new Product("Laptop", 999);
let product2 = new Product("Mouse", 29);
console.log(product1.getInfo());
console.log(product2.getInfo());
10. Logger Class with Message History
- Create a
Logclass with a static array of messages. - Add a static
add()method to append messages. - Add a static
printAll()method to display all logs.
💡 Show Answer
class Log {
private static messages: string[] = [];
private static timestamps: Date[] = [];
static add(message: string): void {
const timestamp = new Date();
Log.messages.push(message);
Log.timestamps.push(timestamp);
console.log(`[${timestamp.toISOString()}] ${message}`);
}
static printAll(): void {
console.log("\n=== All Logs ===");
Log.messages.forEach((msg, index) => {
const time = Log.timestamps[index].toISOString();
console.log(`[${time}] ${msg}`);
});
console.log("================\n");
}
static getLogs(): string[] {
return [...Log.messages];
}
static getLogCount(): number {
return Log.messages.length;
}
static clear(): void {
Log.messages = [];
Log.timestamps = [];
console.log("Logs cleared");
}
static search(keyword: string): string[] {
return Log.messages.filter(msg =>
msg.toLowerCase().includes(keyword.toLowerCase())
);
}
static getLastN(n: number): string[] {
return Log.messages.slice(-n);
}
}
// Usage
Log.add("Application started");
Log.add("User logged in");
Log.add("Database connected");
Log.add("Error: Connection timeout");
Log.add("Retrying connection");
Log.add("Connection successful");
console.log(`\nTotal logs: ${Log.getLogCount()}`);
Log.printAll();
console.log("Searching for 'connection':");
console.log(Log.search("connection"));
console.log("\nLast 3 logs:");
console.log(Log.getLastN(3));
// Enhanced logger with log levels
enum LogLevel {
DEBUG = "DEBUG",
INFO = "INFO",
WARN = "WARN",
ERROR = "ERROR"
}
class Logger {
private static logs: Array<{ level: LogLevel; message: string; timestamp: Date }> = [];
static log(level: LogLevel, message: string): void {
const timestamp = new Date();
Logger.logs.push({ level, message, timestamp });
const timeStr = timestamp.toISOString();
const logStr = `[${timeStr}] [${level}] ${message}`;
// Color output based on level (in actual terminal)
console.log(logStr);
}
static debug(message: string): void {
Logger.log(LogLevel.DEBUG, message);
}
static info(message: string): void {
Logger.log(LogLevel.INFO, message);
}
static warn(message: string): void {
Logger.log(LogLevel.WARN, message);
}
static error(message: string): void {
Logger.log(LogLevel.ERROR, message);
}
static getLogsByLevel(level: LogLevel): string[] {
return Logger.logs
.filter(log => log.level === level)
.map(log => log.message);
}
static printAll(): void {
console.log("\n=== All Logs ===");
Logger.logs.forEach(log => {
console.log(`[${log.timestamp.toISOString()}] [${log.level}] ${log.message}`);
});
console.log("================\n");
}
static clear(): void {
Logger.logs = [];
}
}
// Usage
Logger.debug("Application initializing");
Logger.info("Server started on port 3000");
Logger.warn("High memory usage detected");
Logger.error("Failed to connect to database");
Logger.info("Retrying connection");
Logger.info("Connection successful");
Logger.printAll();
console.log("\nError logs:");
console.log(Logger.getLogsByLevel(LogLevel.ERROR));
Summary
In this chapter, you learned:
✅ What static members are and how they differ from instance members
✅ Creating static properties that belong to the class
✅ Creating static methods for utility functions
✅ Using static members for instance counting
✅ When to use static vs instance members
✅ Factory methods as a use case for static methods
✅ Static initialization blocks for complex setup
✅ Combining static and instance members effectively
✅ Accessing static members correctly
✅ Common patterns: utilities, validators, converters, loggers
Key Takeaways:
- Static members belong to the class, not instances
- Access static members through the class name, not instances
- Use static for utilities that don't need object state
- Use static for tracking class-wide information
- Static methods can't access instance properties
- Instance methods can access static members
- Common uses: counters, utilities, validators, factories
Best Practices:
- Use static for utility functions that don't need state
- Use static readonly for constants
- Keep static properties private when tracking internal state
- Use descriptive names for static members
- Document when static methods are thread-safe
- Prefer static methods over loose utility functions
- Use factory methods for complex object creation
- Don't overuse static - sometimes instances are better
In the next chapter, we'll explore Public vs Private Access and understand encapsulation in depth!
Continue to Chapter 10: Public vs Private Access →