Skip to main content

Understanding Static Properties and Methods in JavaScript

In JavaScript classes, most methods and properties belong to instances. Each object created with new gets its own copy of instance properties and accesses shared methods through the prototype chain. But some functionality belongs to the class itself, not to any particular instance. Configuration defaults, utility functions, factory methods, and instance tracking are all concerns of the class as a whole, not of individual objects.

The static keyword marks methods and properties as belonging to the class constructor rather than to its instances. This guide covers how static members work, how they differ from instance members, how they are inherited through the class hierarchy, and the most common real-world patterns where static members are the right tool.

static Methods: Belonging to the Class, Not Instances

A static method is a method defined on the class itself, not on the prototype. You call it on the class directly, not on an instance.

Basic Syntax

class MathUtils {
static add(a, b) {
return a + b;
}

static multiply(a, b) {
return a * b;
}

static clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
}

// Called on the CLASS
console.log(MathUtils.add(3, 4)); // 7
console.log(MathUtils.multiply(5, 6)); // 30
console.log(MathUtils.clamp(15, 0, 10)); // 10

// NOT available on instances
const utils = new MathUtils();
// utils.add(3, 4); // TypeError: utils.add is not a function

Static methods live on the constructor function itself, not on the prototype:

console.log(typeof MathUtils.add);            // "function"
console.log(typeof MathUtils.prototype.add); // "undefined"

Understanding Where Static Methods Live

class User {
constructor(name) {
this.name = name;
}

// Instance method → lives on User.prototype
greet() {
return `Hi, I'm ${this.name}`;
}

// Static method → lives on User itself
static createAnonymous() {
return new User("Anonymous");
}
}

// Instance method is on the prototype
console.log(User.prototype.hasOwnProperty("greet")); // true
console.log(User.hasOwnProperty("greet")); // false

// Static method is on the constructor
console.log(User.hasOwnProperty("createAnonymous")); // true
console.log(User.prototype.hasOwnProperty("createAnonymous")); // false

const alice = new User("Alice");

// Instance method: available on the instance (via prototype)
console.log(alice.greet()); // "Hi, I'm Alice"

// Static method: NOT available on the instance
// alice.createAnonymous(); // TypeError

this in Static Methods

Inside a static method, this refers to the class itself (the constructor function), not an instance:

class User {
constructor(name) {
this.name = name;
}

static create(name) {
// "this" is the User class (or a subclass, if inherited)
console.log(this === User); // true (when called as User.create)
return new this(name);
}

static className() {
return this.name; // The class's name property
}
}

const alice = User.create("Alice");
console.log(alice.name); // "Alice"
console.log(User.className()); // "User"

This behavior of this in static methods is crucial for inheritance, as we will see later.

Static Methods vs. Regular Functions

You might wonder why not just write standalone functions instead of static methods. Both work, but static methods have advantages:

// Standalone function
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// Static method on a class
class Validator {
static email(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

static required(value) {
return value !== null && value !== undefined && value !== "";
}

static minLength(value, min) {
return typeof value === "string" && value.length >= min;
}

static range(value, min, max) {
return typeof value === "number" && value >= min && value <= max;
}
}

Static methods provide:

  • Organization: Related utilities are grouped under a meaningful name
  • Discoverability: Validator. triggers autocomplete in editors, showing all available validators
  • Inheritance: Subclasses can inherit and override static methods
  • Namespacing: No risk of name collisions with other modules

static Properties

Just like static methods, static properties belong to the class itself, not to instances. They are useful for configuration values, counters, constants, and shared state.

Basic Syntax

class Config {
static API_URL = "https://api.example.com";
static TIMEOUT = 5000;
static MAX_RETRIES = 3;
static VERSION = "2.1.0";

static getEndpoint(path) {
return `${this.API_URL}${path}`;
}
}

console.log(Config.API_URL); // "https://api.example.com"
console.log(Config.TIMEOUT); // 5000
console.log(Config.getEndpoint("/users")); // "https://api.example.com/users"

// Not on instances
const config = new Config();
console.log(config.API_URL); // undefined

Static Properties Are Writable

Unlike const variables, static properties can be reassigned:

class Settings {
static theme = "light";
static language = "en";
}

console.log(Settings.theme); // "light"

Settings.theme = "dark";
console.log(Settings.theme); // "dark"

If you need truly immutable static values, you can freeze them or use getter-only accessors:

class Constants {
static get PI() {
return 3.14159265358979;
}

static get E() {
return 2.71828182845904;
}
}

console.log(Constants.PI); // 3.14159265358979
// Constants.PI = 42; // Silently fails (or throws in strict mode on setter-less property)
console.log(Constants.PI); // 3.14159265358979 (unchanged)

Static Properties vs. Instance Properties

class Counter {
// Static property: shared across ALL instances, belongs to the class
static totalCreated = 0;

// Instance property: unique to EACH instance
count = 0;

constructor(name) {
this.name = name;
Counter.totalCreated++; // Increment the class-level counter
}

increment() {
this.count++; // Increment the instance-level counter
}
}

const a = new Counter("A");
const b = new Counter("B");
const c = new Counter("C");

a.increment();
a.increment();
b.increment();

console.log(a.count); // 2 (only a's count)
console.log(b.count); // 1 (only b's count)
console.log(c.count); // 0 (c was never incremented)

console.log(Counter.totalCreated); // 3 (class-level: total instances created)

The distinction is clear:

  • this.count is per instance. Each object has its own.
  • Counter.totalCreated is per class. There is only one, shared by all instances.

Factory Methods as Static Methods

One of the most common uses of static methods is creating factory methods, alternative constructors that create instances in different ways. Since a class can only have one constructor, factory methods provide additional creation patterns.

Basic Factory Methods

class User {
constructor(firstName, lastName, email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.createdAt = new Date();
}

get fullName() {
return `${this.firstName} ${this.lastName}`;
}

// Factory: create from a full name string
static fromFullName(fullName, email) {
const [firstName, ...rest] = fullName.split(" ");
const lastName = rest.join(" ");
return new this(firstName, lastName, email);
}

// Factory: create from a data object (e.g., API response)
static fromJSON(json) {
const data = typeof json === "string" ? JSON.parse(json) : json;
return new this(data.firstName, data.lastName, data.email);
}

// Factory: create a guest user
static createGuest() {
const id = Math.random().toString(36).slice(2, 8);
return new this("Guest", id, `guest-${id}@example.com`);
}
}

// Different ways to create User instances
const user1 = new User("Alice", "Johnson", "alice@example.com");
const user2 = User.fromFullName("Bob Smith", "bob@example.com");
const user3 = User.fromJSON('{"firstName":"Charlie","lastName":"Brown","email":"charlie@example.com"}');
const user4 = User.createGuest();

console.log(user1.fullName); // "Alice Johnson"
console.log(user2.fullName); // "Bob Smith"
console.log(user3.fullName); // "Charlie Brown"
console.log(user4.fullName); // "Guest a3f8b2" (random)

Using new this() Instead of new User()

Notice the factory methods use new this(...) instead of new User(...). This is intentional and important for inheritance:

class AdminUser extends User {
constructor(firstName, lastName, email) {
super(firstName, lastName, email);
this.role = "admin";
}
}

// Because the factory uses "new this()", it creates the right type
const admin = AdminUser.fromFullName("Jane Doe", "jane@example.com");

console.log(admin instanceof AdminUser); // true
console.log(admin.role); // "admin"
console.log(admin.fullName); // "Jane Doe"

If the factory had used new User(...), calling AdminUser.fromFullName() would create a User instead of an AdminUser. Using new this() ensures the factory respects the class it is called on.

Date-Like Factory Methods

The built-in Date class is a good example of why factory methods are useful. It has one overloaded constructor that tries to handle too many cases. A class with explicit factory methods is clearer:

class DateTime {
constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
this.date = new Date(year, month - 1, day, hours, minutes, seconds);
}

// Factory: from a timestamp
static fromTimestamp(ms) {
const d = new Date(ms);
return new this(
d.getFullYear(), d.getMonth() + 1, d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds()
);
}

// Factory: current date/time
static now() {
return this.fromTimestamp(Date.now());
}

// Factory: from ISO string
static fromISO(isoString) {
return this.fromTimestamp(Date.parse(isoString));
}

// Factory: from individual date components
static fromDate(year, month, day) {
return new this(year, month, day);
}

toString() {
return this.date.toISOString();
}
}

const now = DateTime.now();
const specific = DateTime.fromDate(2024, 6, 15);
const parsed = DateTime.fromISO("2024-03-20T14:30:00Z");
const fromMs = DateTime.fromTimestamp(1700000000000);

console.log(`${now}`); // current time in ISO format
console.log(`${specific}`); // "2024-06-15T..."

Validation in Factory Methods

Factory methods can include validation and return null or throw errors for invalid input, keeping the constructor simple:

class Color {
constructor(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}

static fromHex(hex) {
const match = hex.match(/^#?([0-9a-f]{6})$/i);
if (!match) {
throw new Error(`Invalid hex color: ${hex}`);
}
const [r, g, b] = match[1].match(/.{2}/g).map(s => parseInt(s, 16));
return new this(r, g, b);
}

static fromRGBString(str) {
const match = str.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (!match) {
throw new Error(`Invalid RGB string: ${str}`);
}
return new this(+match[1], +match[2], +match[3]);
}

static tryFromHex(hex) {
try {
return this.fromHex(hex);
} catch {
return null; // Return null instead of throwing
}
}

toHex() {
const toHexPart = n => n.toString(16).padStart(2, "0");
return `#${toHexPart(this.r)}${toHexPart(this.g)}${toHexPart(this.b)}`;
}

toString() {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
}

const red = Color.fromHex("#ff0000");
console.log(red.toString()); // "rgb(255, 0, 0)"

const blue = Color.fromRGBString("rgb(0, 0, 255)");
console.log(blue.toHex()); // "#0000ff"

const invalid = Color.tryFromHex("not-a-color");
console.log(invalid); // null

Inheritance of Static Members

When a class extends another, static methods and properties are inherited along the constructor prototype chain. This is one of the advantages of static methods over standalone functions.

Static Methods Are Inherited

class Animal {
constructor(name) {
this.name = name;
}

static create(name) {
return new this(name);
}

static compare(a, b) {
return a.name.localeCompare(b.name);
}

speak() {
return `${this.name} makes a sound`;
}
}

class Dog extends Animal {
bark() {
return `${this.name} barks!`;
}
}

class Cat extends Animal {
meow() {
return `${this.name} meows!`;
}
}

// Dog and Cat inherit static methods from Animal
const rex = Dog.create("Rex"); // Uses inherited create()
const whiskers = Cat.create("Whiskers");

console.log(rex instanceof Dog); // true ()"new this()" creates a Dog)
console.log(whiskers instanceof Cat); // true ()"new this()" creates a Cat)

// Inherited static compare works on any Animal
const animals = [Dog.create("Rex"), Cat.create("Alpha"), Dog.create("Buddy")];
animals.sort(Animal.compare);
console.log(animals.map(a => a.name)); // ["Alpha", "Buddy", "Rex"]

// Can also call on the subclass
animals.sort(Dog.compare); // Same method, inherited

Static Properties Are Inherited

class Base {
static version = "1.0";
static debug = false;

static setDebug(enabled) {
this.debug = enabled;
}
}

class ChildA extends Base {}
class ChildB extends Base {}

// Inherited values
console.log(ChildA.version); // "1.0" (inherited from Base)
console.log(ChildB.version); // "1.0" (inherited from Base)

// But be careful with mutation!
ChildA.version = "2.0"; // Sets on ChildA, does NOT modify Base

console.log(ChildA.version); // "2.0" (ChildA's own property now)
console.log(ChildB.version); // "1.0" (still inherited from Base)
console.log(Base.version); // "1.0" (unchanged)

Overriding Static Methods

Child classes can override static methods, just like instance methods:

class Model {
static tableName() {
throw new Error("tableName() must be implemented by subclass");
}

static async findById(id) {
const table = this.tableName();
console.log(`SELECT * FROM ${table} WHERE id = ${id}`);
// In real code: return database query result
}

static async findAll() {
const table = this.tableName();
console.log(`SELECT * FROM ${table}`);
}

static async create(data) {
const table = this.tableName();
const columns = Object.keys(data).join(", ");
const values = Object.values(data).map(v => `'${v}'`).join(", ");
console.log(`INSERT INTO ${table} (${columns}) VALUES (${values})`);
}
}

class User extends Model {
static tableName() {
return "users"; // Override
}
}

class Product extends Model {
static tableName() {
return "products"; // Override
}

// Override and extend findAll with a default sort
static async findAll() {
console.log("(Products are sorted by name)");
return super.findAll(); // Call parent's findAll
}
}

User.findById(1);
// SELECT * FROM users WHERE id = 1

User.create({ name: "Alice", email: "alice@example.com" });
// INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')

Product.findAll();
// (Products are sorted by name)
// SELECT * FROM products

The Static Prototype Chain

The inheritance of statics works because extends sets up a prototype chain between the constructors:

// Dog extends Animal

// Instance chain (for instance methods)
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

// Static chain (for static methods and properties)
console.log(Object.getPrototypeOf(Dog) === Animal); // true

When you access Dog.create:

  1. JavaScript checks Dog for a create property: not found
  2. Follows Dog.__proto__ to Animal: found!
  3. Returns Animal.create

And because this inside create is Dog (the class it was called on), new this() creates a Dog instance.

Use Cases: Utility Methods, Instance Tracking, Configuration

Utility Methods

Group related utility functions as static methods on a descriptive class:

class StringUtils {
static capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

static truncate(str, maxLength, suffix = "...") {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - suffix.length) + suffix;
}

static slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_]+/g, "-")
.replace(/^-+|-+$/g, "");
}

static toCamelCase(str) {
return str
.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "");
}

static toKebabCase(str) {
return str
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/[\s_]+/g, "-")
.toLowerCase();
}
}

console.log(StringUtils.capitalize("hello WORLD")); // "Hello world"
console.log(StringUtils.truncate("Long text here", 10)); // "Long te..."
console.log(StringUtils.slugify("Hello World! 123")); // "hello-world-123"
console.log(StringUtils.toCamelCase("background-color")); // "backgroundColor"
console.log(StringUtils.toKebabCase("backgroundColor")); // "background-color"

Instance Tracking

Static properties and methods are perfect for tracking all instances of a class:

class Connection {
static #instances = new Set();
static #maxConnections = 10;

#id;
#connected = false;

constructor(host, port) {
if (Connection.#instances.size >= Connection.#maxConnections) {
throw new Error(
`Connection limit reached (max: ${Connection.#maxConnections})`
);
}

this.#id = Math.random().toString(36).slice(2, 8);
this.host = host;
this.port = port;
Connection.#instances.add(this);
}

connect() {
this.#connected = true;
console.log(`[${this.#id}] Connected to ${this.host}:${this.port}`);
}

disconnect() {
this.#connected = false;
Connection.#instances.delete(this);
console.log(`[${this.#id}] Disconnected`);
}

// Static methods to manage all connections
static getActiveCount() {
return Connection.#instances.size;
}

static disconnectAll() {
for (const conn of Connection.#instances) {
conn.disconnect();
}
}

static setMaxConnections(max) {
Connection.#maxConnections = max;
}

static getAll() {
return [...Connection.#instances];
}
}

const conn1 = new Connection("localhost", 3000);
const conn2 = new Connection("localhost", 3001);
const conn3 = new Connection("db.example.com", 5432);

conn1.connect(); // [a3f8b2] Connected to localhost:3000
conn2.connect(); // [c7d1e9] Connected to localhost:3001

console.log(Connection.getActiveCount()); // 3

conn2.disconnect(); // [c7d1e9] Disconnected
console.log(Connection.getActiveCount()); // 2

Connection.disconnectAll();
console.log(Connection.getActiveCount()); // 0

Configuration and Defaults

Static properties provide a clean way to manage class-level configuration:

class HttpClient {
static baseURL = "";
static defaultHeaders = {
"Content-Type": "application/json"
};
static timeout = 30000;

static configure(options) {
if (options.baseURL !== undefined) this.baseURL = options.baseURL;
if (options.timeout !== undefined) this.timeout = options.timeout;
if (options.headers) {
this.defaultHeaders = { ...this.defaultHeaders, ...options.headers };
}
}

static async request(method, path, options = {}) {
const url = `${this.baseURL}${path}`;
const headers = { ...this.defaultHeaders, ...options.headers };

console.log(`${method} ${url}`);
console.log("Headers:", headers);
console.log("Timeout:", this.timeout);

// In real code: return fetch(url, { method, headers, ... });
}

static get(path, options) {
return this.request("GET", path, options);
}

static post(path, body, options) {
return this.request("POST", path, { ...options, body });
}
}

// Configure once
HttpClient.configure({
baseURL: "https://api.example.com",
timeout: 10000,
headers: { "Authorization": "Bearer token123" }
});

// Use throughout the application
HttpClient.get("/users");
// GET https://api.example.com/users
// Headers: { Content-Type: application/json, Authorization: Bearer token123 }
// Timeout: 10000

Singleton Pattern

Static methods can enforce the singleton pattern, ensuring only one instance of a class exists:

class Database {
static #instance = null;

#connection;

constructor(connectionString) {
if (Database.#instance) {
throw new Error("Use Database.getInstance() instead of new Database()");
}
this.#connection = connectionString;
Database.#instance = this;
}

static getInstance(connectionString) {
if (!Database.#instance) {
new Database(connectionString);
}
return Database.#instance;
}

static resetInstance() {
Database.#instance = null;
}

query(sql) {
console.log(`Executing on ${this.#connection}: ${sql}`);
}
}

const db1 = Database.getInstance("postgresql://localhost:5432/mydb");
const db2 = Database.getInstance("postgresql://other:5432/other");

console.log(db1 === db2); // true (same instance)

db1.query("SELECT * FROM users");
// Executing on postgresql://localhost:5432/mydb: SELECT * FROM users

Type Checking and Validation

Static methods serve as validators or type guards for a class:

class Temperature {
#celsius;

constructor(celsius) {
if (!Temperature.isValid(celsius)) {
throw new RangeError(`Invalid temperature: ${celsius}`);
}
this.#celsius = celsius;
}

get celsius() { return this.#celsius; }
get fahrenheit() { return this.#celsius * 9/5 + 32; }
get kelvin() { return this.#celsius + 273.15; }

// Static validation
static isValid(celsius) {
return typeof celsius === "number" && !isNaN(celsius) && celsius >= -273.15;
}

// Static comparison
static isEqual(t1, t2) {
return t1.celsius === t2.celsius;
}

static max(...temperatures) {
return temperatures.reduce((max, t) =>
t.celsius > max.celsius ? t : max
);
}

static min(...temperatures) {
return temperatures.reduce((min, t) =>
t.celsius < min.celsius ? t : min
);
}

// Factory methods for common temperatures
static freezing() { return new this(0); }
static boiling() { return new this(100); }
static bodyTemp() { return new this(37); }
static absoluteZero() { return new this(-273.15); }

toString() {
return `${this.#celsius}°C`;
}
}

const body = Temperature.bodyTemp();
const boiling = Temperature.boiling();
const freezing = Temperature.freezing();

console.log(`${body}`); // "37°C"
console.log(body.fahrenheit); // 98.6

const hottest = Temperature.max(body, boiling, freezing);
console.log(`Hottest: ${hottest}`); // "Hottest: 100°C"

console.log(Temperature.isValid(-300)); // false (below absolute zero)
console.log(Temperature.isValid(25)); // true

Registry Pattern

Static members can maintain a registry of subclasses or variations:

class Serializer {
static #registry = new Map();

static register(type, serializerClass) {
this.#registry.set(type, serializerClass);
}

static getSerializer(type) {
const SerializerClass = this.#registry.get(type);
if (!SerializerClass) {
throw new Error(`No serializer registered for type: ${type}`);
}
return new SerializerClass();
}

serialize(data) {
throw new Error("serialize() must be implemented");
}

deserialize(str) {
throw new Error("deserialize() must be implemented");
}
}

class JSONSerializer extends Serializer {
serialize(data) { return JSON.stringify(data); }
deserialize(str) { return JSON.parse(str); }
}

class CSVSerializer extends Serializer {
serialize(data) {
if (!Array.isArray(data) || data.length === 0) return "";
const headers = Object.keys(data[0]);
const rows = data.map(obj => headers.map(h => obj[h]).join(","));
return [headers.join(","), ...rows].join("\n");
}

deserialize(str) {
const [headerLine, ...rows] = str.split("\n");
const headers = headerLine.split(",");
return rows.map(row => {
const values = row.split(",");
return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
});
}
}

// Register serializers
Serializer.register("json", JSONSerializer);
Serializer.register("csv", CSVSerializer);

// Use the registry
const data = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 }
];

const jsonSerializer = Serializer.getSerializer("json");
console.log(jsonSerializer.serialize(data));
// [{"name":"Alice","age":30},{"name":"Bob","age":25}]

const csvSerializer = Serializer.getSerializer("csv");
console.log(csvSerializer.serialize(data));
// name,age
// Alice,30
// Bob,25

Common Mistakes

Mistake 1: Trying to Access Static Members on Instances

class Counter {
static count = 0;

constructor() {
Counter.count++;
}
}

new Counter();
new Counter();

// WRONG: accessing static via instance
const c = new Counter();
console.log(c.count); // undefined (not on the instance!)

// CORRECT: access via class name
console.log(Counter.count); // 3

Mistake 2: Using this to Access Statics Inconsistently

class Example {
static value = 42;

static showValue() {
// Both work in static methods, but "this" is better for inheritance
console.log(Example.value); // Works but doesn't support inheritance
console.log(this.value); // Works and supports inheritance
}

instanceMethod() {
// In instance methods, "this" is the instance, NOT the class
// console.log(this.value); // undefined!
console.log(Example.value); // Works
console.log(this.constructor.value); // Also works (accesses the class via instance)
}
}
tip

In static methods, use this to reference the class (supports inheritance). In instance methods, use this.constructor or the class name directly to access static members.

Mistake 3: Mutating Inherited Static Properties

class Parent {
static config = { debug: false, level: "info" };
}

class Child extends Parent {}

// This modifies the SHARED object, affecting Parent too!
Child.config.debug = true;

console.log(Parent.config.debug); // true (Parent was affected!)

// Fix: create a new object when overriding
class SafeChild extends Parent {
static config = { ...Parent.config, debug: true };
}

console.log(SafeChild.config.debug); // true
console.log(Parent.config.debug); // true (already mutated from above)

When a static property holds an object, child classes inherit a reference to the same object. Mutating it affects the parent. Always create a new object when overriding.

Summary

ConceptKey Takeaway
static methodsBelong to the class itself; called as ClassName.method(); not available on instances
static propertiesData stored on the class; shared across all code; not on instances
this in static methodsRefers to the class (constructor function), not an instance
Factory methodsStatic methods that create instances; use new this() for inheritance support
Static inheritanceChild classes inherit static members via the constructor prototype chain
new this()In static factory methods, creates an instance of the actual class (works with subclasses)
Instance trackingStatic properties can store Sets or Maps of all created instances
ConfigurationStatic properties provide class-level defaults and settings
SingletonStatic methods can enforce single-instance patterns
RegistryStatic Maps can store and retrieve subclasses or strategies

Static methods and properties give classes a dual nature: they are both blueprints for instances (through the constructor and prototype methods) and organized namespaces (through static members). Use statics for anything that conceptually belongs to the class as a whole rather than to individual objects: creation logic, utility functions, shared configuration, instance management, and type-level operations.