Skip to main content

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:​

  1. 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;
}
}
  1. Creating utility functions
class DateUtils {
static formatDate(date: Date): string {
return date.toISOString().split("T")[0];
}
}
  1. Tracking class-wide information
class BankAccount {
private static totalAccounts: number = 0;

constructor() {
BankAccount.totalAccounts++;
}

static getTotalAccounts(): number {
return BankAccount.totalAccounts;
}
}

Use Instance When:​

  1. 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;
}
}
  1. 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 MathHelper class 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 pi to 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 Counter class with a static count property.
  • 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 Config class with static properties for app settings.
  • Include appName, version, and maxUsers.
  • 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 User class 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 Validator class with static validation methods.
  • Include isEmail() and isNonEmpty().
  • 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 BankAccount class with static accountCount.
  • 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 TemperatureConverter class.
  • 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 Log class 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 →