Static & Private Members Interview Questions & Answers
24 questions Updated 2026-06-18
JavaScript static and private class member interview questions — static methods and fields, private fields and methods, static blocks, and encapsulation patterns.
Read the in-depth guideJavaScript Static & Private Class Members — Fields, Methods, and True EncapsulationMembers (methods or fields) that belong to the class itself, not to
instances. Accessed as ClassName.member.
class MathUtil {
static PI = 3.14159
static square(n) { return n * n }
}
MathUtil.PI // 3.14159
MathUtil.square(4) // 16
new MathUtil().square // undefined — not on instances
Statics suit utilities and class-level data that don't depend on a particular instance. They're shared (one copy on the class) and inherited by subclasses.
For operations related to the class but not to a specific instance — most commonly factory methods and utilities.
class User {
constructor(name) { this.name = name }
static fromJSON(json) { return new User(JSON.parse(json).name) }
static compare(a, b) { return a.name.localeCompare(b.name) }
}
const u = User.fromJSON('{"name":"Ada"}')
Static factory methods (User.fromJSON, Array.from, Promise.resolve) are a
common alternative to overloaded constructors. this inside a static method is
the class.
Class-level properties declared with static, shared across all instances and
the class — useful for constants, counters, or caches.
class Counter {
static count = 0 // shared
constructor() { Counter.count++ }
}
new Counter(); new Counter()
Counter.count // 2
Reference them via the class name (Counter.count), not this (in instance
methods this is the instance). Static fields are a newer feature; older code
used Class.prop = value after the class.
The class itself — so static methods can call sibling statics and respect subclassing.
class Base {
static create() { return new this() } // `this` is the class
}
class Sub extends Base {}
Base.create() // Base instance
Sub.create() // Sub instance — `this` is Sub
Because this is the concrete class, a static factory like create() builds an
instance of whichever subclass it was called on — useful for polymorphic
factories.
Fields prefixed with # are truly private — accessible only inside the
class body, enforced by the language (not a convention).
class Account {
#balance = 0
deposit(n) { this.#balance += n }
get balance() { return this.#balance }
}
const a = new Account()
a.#balance // SyntaxError outside the class
a.balance // via the getter
Unlike the _underscore convention, #private can't be accessed or even
detected from outside. It's real encapsulation built into the syntax.
_name is a convention — a hint that a member is internal, but it's still
fully public and accessible. #name is enforced privacy — a SyntaxError to
access from outside.
class C {
_soft = 1 // accessible: c._soft works (just "please don't")
#hard = 2 // c.#hard -> SyntaxError
}
Prefer #private for genuine encapsulation. The _ convention persists in older
code and where subclass access is wanted (subclasses can't see #private).
Yes — prefix a method (or getter/setter) with # to make it callable only from
within the class.
class Service {
#validate(data) { return !!data } // private method
get #ready() { return this.#validate(this.data) } // private getter
submit(data) {
this.data = data
if (this.#validate(data)) { /* ... */ }
}
}
Private methods keep internal helpers off the public API and prevent subclasses or callers from depending on them. They live per-class, not on the public prototype.
Yes — combine static and # for private class-level fields and methods,
accessible only inside the class.
class IdGenerator {
static #counter = 0
static #next() { return ++IdGenerator.#counter }
static create() { return { id: IdGenerator.#next() } }
}
IdGenerator.#counter // SyntaxError
Static privates are great for internal class state (caches, counters, registries) that shouldn't be touched from outside. Reference them via the class name inside the body.
A static { } block (ES2022) runs once when the class is defined, for complex
static setup that a field initializer can't express — and it can access private
static members.
class Config {
static settings = {}
static {
const env = readEnv() // run setup logic
this.settings = parse(env)
this.#secret = deriveSecret(env) // can touch private statics
}
static #secret
}
It's the class equivalent of a one-time initializer, useful when static state needs computation, multiple statements, or try/catch at definition time.
Use the #field in obj syntax (the "ergonomic brand check") — it returns whether
obj has that private field, without throwing.
class Stream {
#buffer = []
static isStream(obj) { return #buffer in obj }
}
Stream.isStream(new Stream()) // true
Stream.isStream({}) // false
This is the reliable way to detect "is this a real instance of my class" — more
robust than instanceof (which can be spoofed via prototypes). Accessing
obj.#buffer directly on a non-instance would throw, so the in form is used.
It hides internal state so callers can't depend on or corrupt it, letting you change the implementation freely and enforce invariants in one place.
class Temperature {
#celsius = 0
set celsius(v) {
if (v < -273.15) throw new RangeError('below absolute zero')
this.#celsius = v // validation can't be bypassed
}
get celsius() { return this.#celsius }
}
With public fields, anyone could set an invalid value directly. Private fields + accessors guarantee the validation runs and keep the public surface small and stable.
No — #private fields are not accessible from subclasses. They belong
strictly to the class that declares them.
class Base { #secret = 1; getSecret() { return this.#secret } }
class Sub extends Base {
reveal() { return this.#secret } // SyntaxError — not visible
}
This is stricter than protected in other languages. For state subclasses need,
use the _ convention or expose a protected-style accessor. Private fields are
truly class-local.
Yes — subclasses inherit static methods and fields via the class's own prototype
chain (Sub.__proto__ === Base).
class Base { static greet() { return 'hi' } }
class Sub extends Base {}
Sub.greet() // 'hi' — inherited static
Inside an inherited static, this is the calling subclass, and super reaches
the parent's static. Static fields are inherited as references — careful, since
a shared static object is shared across the hierarchy.
Yes — every private field must be declared in the class body. You can't create one dynamically like a public property.
class C {
#x = 1 // must declare
m() { this.#y = 2 } // SyntaxError — #y not declared
}
This is unlike public properties, which can be added anywhere. The requirement lets the engine know the full set of private fields at parse time (enabling the brand check and strong encapsulation).
Cache the instance in a private static field and expose it via a static getter or method, with a guarded/private constructor.
class Logger {
static #instance
static get instance() {
return Logger.#instance ??= new Logger()
}
}
Logger.instance === Logger.instance // true
The private static #instance holds the single object; ??= lazily creates it
once. (In JS, a module-level singleton is often simpler, but this is the
class-based form.)
Static factories can have descriptive names, return cached/subclass instances, do async work indirectly, and offer multiple "constructors" — things a single constructor can't.
class Color {
constructor(r, g, b) { /* ... */ }
static fromHex(hex) { /* parse */ return new Color(/* ... */) }
static fromHSL(h, s, l) { /* convert */ return new Color(/* ... */) }
}
Named factories (fromHex, fromHSL) read better than overloading one
constructor and let you control instance creation (caching, validation). A widely
used OOP idiom.
Yes — get #name() / set #name(v) define private accessors, usable only inside
the class for computed private values.
class Box {
#w = 0; #h = 0
get #area() { return this.#w * this.#h } // private computed value
report() { return `area: ${this.#area}` }
}
Private accessors are handy for internal derived values you don't want to expose
or recompute manually. Like all # members, they're inaccessible and invisible
outside the class.
With closures (per-instance functions) or a module-scoped WeakMap keyed by
instance — both hide data from outside the class.
const _balance = new WeakMap()
class Account {
constructor() { _balance.set(this, 0) }
deposit(n) { _balance.set(this, _balance.get(this) + n) }
get balance() { return _balance.get(this) }
}
The WeakMap lives in module scope, so only the class's methods can read it, and
entries are garbage-collected with the instance. #private fields now do this
natively and more cleanly.
Use a static method when the function is conceptually tied to the class
(factories, class-specific helpers, polymorphic via this). Use a plain module
function for standalone utilities not bound to a class.
// tied to the class -> static
class Vector { static add(a, b) { /* ... */ } }
// generic utility -> module function
export function clamp(n, lo, hi) { /* ... */ }
Don't create a class full of only static methods just to namespace functions — a module is the idiomatic JS namespace. Statics belong with classes that also have instances.
A #private method lives on the class once (shared), whereas a closure helper
defined per instance (or inside the constructor) is duplicated per object. Private
methods give encapsulation and memory efficiency.
class Service {
#parse(x) { /* shared across instances */ } // one definition
}
// vs a per-instance closure in the constructor (one function per object)
So #private methods are usually better than closure-based privacy for classes:
same hiding, no per-instance function cost.
Use static fields (often on a frozen class) to model a fixed set of named constants.
class Direction {
static North = new Direction('N')
static South = new Direction('S')
constructor(code) { this.code = code }
}
Object.freeze(Direction)
Direction.North.code // 'N'
JavaScript has no native enum, so frozen static instances (or a frozen plain
object Object.freeze({ North: 'N', ... })) are the common patterns for
enumerations.
Inside an instance method, this is the instance, so this.staticField is
undefined — you must use the class name (or this.constructor).
class C {
static MAX = 10
check(n) {
return n < this.MAX // undefined — MAX is static
// return n < C.MAX //
// return n < this.constructor.MAX // subclass-aware
}
}
Use ClassName.field for a fixed reference, or this.constructor.field if you
want subclasses to be able to override the static value.
Private fields are "branded" to their class. Reading obj.#field on an object
that isn't an instance throws a TypeError, which protects encapsulation but
means you should guard with #field in obj first.
class A { #x = 1; static read(o) { return #x in o ? o.#x : null } }
A.read(new A()) // 1
A.read({}) // null (guarded; direct o.#x would throw)
The throw-on-mismatch behavior is what makes #field in obj the safe brand
check, and prevents one class's private accessor from working on foreign objects.
Freeze the class (and/or its static objects) so static fields can't be reassigned after definition.
class Config {
static API = '/v1'
static TIMEOUT = 5000
}
Object.freeze(Config)
Config.API = '/v2' // ignored (throws in strict mode)
Object.freeze(Config) locks top-level static props; nested static objects need
their own freeze (it's shallow). A static {} block plus Object.freeze is a
clean way to set up immutable class-level config.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.