Most properties and methods belong to a specific instance — car1.speed is different from car2.speed. A static (or 'class-level') member instead belongs to the class itself, shared by every instance, and accessible without creating any object at all.
class Car {
static totalCarsCreated = 0 // shared across ALL Car instances
speed = 0 // unique to each instance
function __construct() {
Car.totalCarsCreated += 1 // increments the shared counter
}
static function getTotalCreated() {
return Car.totalCarsCreated
}
}
car1 = new Car()
car2 = new Car()
car3 = new Car()
print(Car.getTotalCreated()) // 3 — one shared value, not per-instance
Notice Car.totalCarsCreated is accessed on the class, not on any instance (Car.x, not car1.x), signaling it's shared, global state for that class rather than per-object data.
Common legitimate uses for static members:
- Counters and registries tracking something across all instances (as above)
- Constants —
Math.PI,Color.RED— values that never change and don't need per-instance storage - Utility / helper methods that don't need any instance state —
Math.max(a, b),StringUtils.capitalize(str) - Factory methods — static methods that construct and return new instances, often with more descriptive names than a raw constructor:
User.createGuest()
A caution: overusing static state for things that should really be per-instance data is a common anti-pattern — it effectively recreates the same 'everything is global' problems OOP encapsulation is meant to solve, just at the class level instead of the whole-program level.