Beginner10 min readObject-Oriented Foundationslive prototype

Static vs Instance Members

Instance members belong to each object (one copy per object); static members belong to the class itself (one shared copy for all).

The idea

What it is

Some data belongs to each object, and some data belongs to the class as a whole. An instance member gets a fresh copy for every object you create — your name, your salary. A static member exists exactly once, on the class itself, and every object shares that single copy.

Picture a company. Each employee has their own name badge and own salary — change yours and mine is untouched. But there's one company name printed on the wall that everyone shares. Rename the company and it changes for all of you at once, because there was only ever one of it. Instance = your own badge; static = the sign on the wall.

The one sentence to remember

Instance members live on the object (one per object); static members live on the class (one for everyone). You reach an instance member through an object (alice.salary), and a static member through the class (Employee.company).

Mechanics

How it works

Instance members: one copy per object

An instance field is declared on the class but allocated per object — every new Employee(...) gets its own slot for name and salary. Inside a method, you reach the current object's own copy through this: this.salary. Calling alice.giveRaise() touches only Alice's salary; Bob's is completely separate. That independence is the whole point of objects.

instance.ts
class Employee {
  name: string;
  salary: number; // instance field — one per object

  constructor(name: string, salary: number) {
    this.name = name;
    this.salary = salary; // each object gets its OWN salary
  }

  giveRaise(amount: number) {
    this.salary += amount; // 'this' = the specific object
  }
}

const alice = new Employee("Alice", 100);
const bob   = new Employee("Bob", 90);
alice.giveRaise(10); // alice.salary → 110, bob.salary still 90

Static members: one copy, shared by all

A static member belongs to the class, not to any object. There is exactly one copy no matter how many objects exist — or even if zero exist. You don't need this, because there's no particular object involved; you call it on the class name itself, like Employee.company or Employee.headcount(). Change it once and every object that reads it sees the new value, because they're all reading the same single box.

  • Static field — shared data: a company name, a configuration constant, a running counter of how many objects exist.
  • Static method — behavior that doesn't need a specific object: a factory like Employee.fromCsv(...), or a helper like Math.max(...).
  • No this — a static method can't read this.salary, because which object's salary would it mean? There isn't one.
static.ts
class Employee {
  static company = "Acme";   // ONE shared box for every employee
  static count = 0;          // a static counter

  name: string;
  constructor(name: string) {
    this.name = name;
    Employee.count++;        // bump the shared counter on each hire
  }

  static headcount() {       // static method — no 'this'
    return Employee.count;
  }
}

const a = new Employee("Alice");
const b = new Employee("Bob");
Employee.company = "Globex"; // change ONCE → every employee sees "Globex"
Employee.headcount();        // 2 — read off the class, not an object

When static makes sense

Static shines when the data or behavior truly belongs to the concept, not to any one object: a counter of live instances, a factory method that builds objects, a constant like Math.PI, or a stateless utility function. The test: ask "would this value differ from object to object?" If no — it's static.

The classic gotcha: static is shared mutable state

A mutable static field is effectively a global variable wearing a class's clothes. Because everyone shares the one copy, a change anywhere is visible everywhere — which makes behavior depend on hidden, order-sensitive state that's hard to test and unsafe across threads. Use static state sparingly, and prefer it for constants and counters, not for things that change in surprising ways.

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

The Employee class card holds one shared box — Employee.company — that belongs to the class. Press Hire employee to stamp instance cards, each with its own name and salary. Give one employee a raise and only that card changes; rebrand the company and every card updates at once. One shared box vs many own boxes.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Give a raise — only one box changes

Two employees are seeded on load. Click give raise on just one card and watch only that employee's salary climb while the other stays put. The console narrates instance: alice.salary 100→110 (only alice). Each object owns its own salary.

try 02

Rebrand the company — every box changes

Type a new name into the class-level Rebrand company field and press the button. Notice that Employee.company updates and every employee card instantly shows the new company. The console logs static: Employee.company → 'Globex' (all employees updated). There's only one shared copy.

try 03

Watch the static counter

Press Hire employee and keep an eye on Employee.headcount in the class card — it ticks up on every hire because the counter lives on the class, not on any one object. Compare it to salary, which is per-employee. Counter = static; salary = instance.

In practice

When to use it — and what trips people up

Reach for a static member when…

  • The value is the same for every object and belongs to the concept, not an instance — a company name, an app-wide config, a constant like Math.PI.
  • You're counting instances or holding a shared registryEmployee.headcount, a cache, a connection pool.
  • You need a factory method that builds objects before any object exists — User.fromJson(...), Logger.getInstance().
  • The function is a pure utility with no per-object state — Math.max(a, b), StringUtils.capitalize(s).

Default to instance; promote to static deliberately

If a value could ever differ between two objects, it's instance data — keep it on the object. Reach for mutable static state only when the sharing is genuinely intended, because once it's static, every object is coupled to that single copy.

What it gives you

  • One source of truth for data that's truly shared — change it once, everyone sees it.
  • No memory wasted on a duplicate copy in every object; constants and config live in a single place.
  • Factory methods and utilities are reachable without first creating an object.
  • Static counters and registries give the class a clean, central place to track all its instances.

Common mistakes

  • Mutable static state is hidden global state — any code can change it and every object is affected.
  • Hard to test and mock: tests share the one static value, so state leaks between them and order starts to matter.
  • Not thread-safe by default — concurrent writes to a shared static field can corrupt it without explicit synchronization.
  • Encourages tight coupling: callers depend on a global class rather than on an object they were handed.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// static = shared by the class · instance = per object. Pick a language above.
class Employee {
  static company = "Acme";   // ONE shared copy for every employee
  static headcount = 0;      // a static counter

  // instance fields — one copy PER object
  constructor(public name: string, public salary: number) {
    Employee.headcount++;    // bump the shared counter on each hire
  }

  giveRaise(amount: number) {
    this.salary += amount;   // 'this' → only THIS employee's salary
  }

  // static method — no 'this'; called on the class
  static rebrand(name: string) {
    Employee.company = name; // changes the shared copy for everyone
  }
}

const alice = new Employee("Alice", 100);
const bob   = new Employee("Bob", 90);
alice.giveRaise(10);         // alice.salary → 110, bob still 90 (own box)
Employee.rebrand("Globex");  // every employee now reports "Globex" (shared)
Employee.headcount;          // 2 — read off the class itself

References & further reading

5 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Nothing is scored or saved.

question 01 / 04

You create alice and bob from Employee, then call alice.giveRaise(10). What happens to bob.salary?

question 02 / 04

Employee.company is a static field set to "Acme". You change it to "Globex". What do existing employee objects report as their company?

question 03 / 04

Why can't a static method use this?

question 04 / 04

Which is the biggest risk of using a mutable static field?

0/4 answered