HOWTO · JavaScript

Static Variables in JavaScript

Learn how JavaScript class fields, function properties, and closures retain state between calls.

On this page

JavaScript can retain a value between calls with a public static class field, a property on a function object, or a local variable kept alive by a closure. These patterns all preserve state, but they differ in where the state belongs and who can access it. Use a class field for state shared by a class, a function property for simple public function-level state, or a closure when callers should use the value without reading or changing its binding directly.

What a static variable means in JavaScript

A static variable is state associated with a function or class rather than a particular call. JavaScript does not have one universal static-variable declaration for every function. Instead, it offers several patterns. The static keyword declares a class field or method on the class itself. A property assigned to a function also belongs to that function object. A closure keeps a local binding available to an inner function after the outer function has returned.

These meanings are related, but the mechanisms are not interchangeable in every respect. Class fields and function properties are accessible through their owning function or class object. A closure can keep its binding private and expose only the operations you choose.

Use a static class field for class-wide state

A public static class field belongs to the class, so every instance refers to the same field through the class name. It is not copied into each instance. This syntax requires a runtime that supports public static class fields. If your runtime does not support it, use the function-property or closure pattern below.

Run this example:

class VisitCounter {
  static count = 0;

  static recordVisit() {
    return ++VisitCounter.count;
  }
}

console.log(VisitCounter.recordVisit());
console.log(VisitCounter.recordVisit());
console.log(VisitCounter.count);
1
2
2

Both calls update the same VisitCounter.count. Because the field is public, code can also read or assign it as VisitCounter.count; use a private static field (with a # name) when the class itself should control access. A static member is accessed on the class, not on an instance such as new VisitCounter().

Attach a property to a function

Functions are objects, so a named function can carry a property that persists across calls. This is a compact option for a counter or cache tied to one function. The property is public: other code can inspect or change nextId.counter, so this pattern does not provide privacy.

function nextId() {
  nextId.counter = (nextId.counter ?? 0) + 1;
  return nextId.counter;
}

console.log(nextId());
console.log(nextId());
console.log(nextId.counter);
1
2
2

The same idea applies to a constructor function. A property such as Widget.count belongs to the constructor function, while widget.name belongs to one instance. Instances do not each receive a copy of Widget.count; code accesses that shared property through Widget. This resembles class-wide state in its ownership, but JavaScript function properties do not provide all the access controls or type guarantees that some class systems provide.

Use a closure for private state

A closure lets an inner function keep using a local binding after the outer function finishes. An IIFE (Immediately Invoked Function Expression) creates that outer scope immediately and returns an operation that can update the retained value. Since the binding itself is not placed on a public object, outside code cannot directly read or assign it.

const counter = (function () {
  let count = 0;
  return function () {
    return ++count;
  };
})();

console.log(counter());
console.log(counter());
1
2

Here, counter closes over count; each call to counter() increments that same retained value, so the calls print 1 and then 2. The value is private, while counter is the public interface. To create independent state, invoke a separate factory function that creates and returns a new closure; calling this returned counter only increments its existing state. If callers need to reset or inspect the count, return a small object with explicit methods rather than exposing the binding itself.

Why to avoid arguments.callee

arguments.callee refers to the currently executing function through its arguments object. Older patterns used it to attach a property to that function, much like a named function property. It is a legacy technique and is forbidden in strict mode, so code using it can fail when strict mode applies, including in ECMAScript modules. Named functions make the relationship clearer, and closures provide private state without relying on this restricted property. Prefer one of the supported patterns above instead of building new code around arguments.callee.