Back to Polymorphism
Q7Question 7 of 7

What is static binding vs dynamic binding?

Key Concepts
  • Static binding resolves method calls at compile time based on the reference type (early binding)
  • Dynamic binding resolves method calls at runtime based on the actual object type via the vtable (late binding)
  • Static binding is used for private, static, final methods and overloaded method resolution
  • Dynamic binding is used for overridden instance methods, enabling runtime polymorphism
  • Parent p = new Child(); p.method() calls Child's version because the JVM dispatches via the vtable at runtime
  • Static binding is faster (no vtable lookup), dynamic binding enables polymorphism

Definition

Static binding resolves at compile time using the reference type, for private, static, final, and overloaded methods. Dynamic binding resolves at runtime using the actual object type via the vtable, for overridden instance methods.

Two binding mechanisms determine which method code runs when a call is made.

  • Static binding (early binding): resolved at compile time. The compiler looks at the reference type and binds directly. Used for private methods (not inherited, no override), static methods (class-level, no vtable), final methods (sealed, no override), and overloaded methods (compiler picks signature by argument types).
  • Dynamic binding (late binding): resolved at runtime. The JVM looks at the actual object type in the heap and dispatches via the virtual method table (vtable). Used for overridden instance methods.
  • Parent p = new Child(); p.method() calls Child's version because the JVM checks p's actual type (Child) at runtime, not the variable type (Parent).
  • Static binding is faster (no vtable lookup). Dynamic binding enables polymorphism.

Static binding: the bank's directory at National Bank of India says for accounts, go to Counter 1. You look at the directory (compile time) and go there. It does not matter who is sitting at Counter 1 today. Dynamic binding: you call customer service and say I need help with my account. The system checks your actual account type (savings or current) at that moment and routes you to the right specialist. The routing happens at the moment of the call (runtime), not when the phone system was installed (compile time).

Understanding the Concepts

What is static binding vs dynamic binding?

Binding determines which method implementation is called. Static binding makes this decision at compile time. Dynamic binding makes it at runtime.

Which methods use static binding?

Four types of method calls use static binding: private, static, final, and overloaded.

JAVA
package com.bank.core;

import java.math.BigDecimal;

public class Account {
    // Static binding: private (not inherited, no vtable entry)
    private void audit() { System.out.println("Account audit"); }

    // Static binding: static (class-level, no vtable)
    public static String getPolicy() { return "Standard Policy"; }

    // Static binding: final (sealed, no override possible)
    public final String getAccountId() { return "ACC-BASE"; }

    // Dynamic binding: instance method (can be overridden, vtable dispatch)
    public String getDescription() { return "Generic Account"; }

    // Static binding: overloaded methods (compiler picks by arg types)
    public void deposit(BigDecimal amount) { }
    public void deposit(BigDecimal amount, String note) { }

    public void callAudit() { audit(); } // private -> static binding
}

public class SavingsAccount extends Account {
    @Override public String getDescription() { return "Savings Account"; }
    public static String getPolicy() { return "Savings Policy"; } // hiding
}

Which methods use dynamic binding?

Overridden instance methods use dynamic binding. The JVM resolves them at runtime through the vtable.

JAVA
Account ref = new SavingsAccount();

// Dynamic binding (runtime: actual type = SavingsAccount)
ref.getDescription();
// Compiler: Account has getDescription()? Yes -> valid.
// Runtime: JVM checks actual type -> SavingsAccount -> vtable -> SavingsAccount.getDescription()
// Result: "Savings Account" (Child's version)

// Static binding (compile-time: reference type = Account)
ref.getPolicy();        // "Standard Policy" (Account's static method)
ref.getAccountId();     // "ACC-BASE" (Account's final method)
ref.callAudit();        // "Account audit" (private method, static binding)
ref.deposit(amount);    // compiler picks overload 1 at compile time

How does the vtable work internally?

The JVM creates a vtable for each class at class loading time. The vtable maps method signatures to actual method implementations.

JAVA
// Account vtable:
// getDescription() -> Account.getDescription
// deposit(BigDecimal) -> Account.deposit

// SavingsAccount vtable:
// getDescription() -> SavingsAccount.getDescription  (overridden)
// deposit(BigDecimal) -> Account.deposit              (inherited, same)

// When: Account ref = new SavingsAccount()
// ref.getDescription() -> JVM looks at actual object -> SavingsAccount
// -> checks SavingsAccount vtable -> getDescription() -> SavingsAccount.getDescription()

// private, static, final methods are NOT in the vtable:
// -> resolved at compile time -> no vtable lookup -> faster

Why does Parent p = new Child(); p.method() call Child's version?

Because the compiler generates invokevirtual Parent.method() (valid: Parent has the method). At runtime, the JVM checks p's actual object type (Child), looks up Child's vtable, finds Child.method(), and calls it. The variable type (Parent) determined compile-time validity. The actual type (Child) determined runtime dispatch.

How do the bytecode instructions differ?

| Instruction | Binding | Used for |
|------------|---------|----------|
| invokevirtual | Dynamic (vtable) | Instance method calls |
| invokeinterface | Dynamic (itable) | Interface method calls |
| invokestatic | Static | Static method calls |
| invokespecial | Static | Constructors, super.method(), private methods |

What are common mistakes?

The most common mistake is assuming all method calls go through dynamic binding.

JAVA
// WRONG assumption: private methods dispatch dynamically
// Private methods use invokespecial: static binding. No vtable.

// WRONG assumption: static methods dispatch dynamically
// Account ref = new SavingsAccount();
// ref.getPolicy() returns "Standard Policy" (Account's version)
// Static methods use invokestatic: no vtable lookup.

// WRONG assumption: final methods have overhead from vtable lookup
// The JVM can optimize final methods because no override exists.

What should a senior engineer know?

  • All instance method calls go through dynamic binding (invokevirtual) except private, which uses invokespecial.
  • HotSpot can devirtualize monomorphic call sites (only one implementation exists) and inline the method body for performance.
  • The JIT compiler uses class hierarchy analysis (CHA) to determine if a virtual call can be devirtualized.
  • Interface method calls use invokeinterface with an itable (interface method table) instead of vtable, which is slightly slower.

Visual Overview

DIAGRAM
  Account ref = new SavingsAccount();

  STATIC BINDING                    DYNAMIC BINDING
  (compile-time)                    (runtime)
  ┌────────────────────┐           ┌────────────────────┐
  │ ref.getPolicy()     │           │ ref.getDescription()│
  │ ref.getAccountId()  │           │                    │
  │ ref.callAudit()     │           │ JVM at runtime:    │
  │                     │           │ actual type?        │
  │ Compiler binds to   │           │ SavingsAccount     │
  │ Account at compile  │           │      │             │
  │ time.               │           │      ▼             │
  │                     │           │ vtable lookup      │
  │ No vtable lookup.   │           │      │             │
  │ Faster.             │           │      ▼             │
  │                     │           │ SavingsAccount     │
  │                     │           │ .getDescription()  │
  └────────────────────┘           └────────────────────┘

  METHOD TYPE -> BINDING
  private    -> static  (invokespecial)
  static     -> static  (invokestatic)
  final      -> static  (invokevirtual, but JVM can inline)
  overloaded -> static  (compiler picks signature)
  overridden -> dynamic (invokevirtual, vtable dispatch)

Key Takeaway

Static binding resolves method calls at compile time. The compiler looks at the reference type and binds directly. Used for private methods (not inherited, no vtable entry), static methods (class-level, no vtable), final methods (sealed, JVM can inline), and overloaded methods (compiler picks signature by argument types). Dynamic binding resolves at runtime. The JVM looks at the actual object type in the heap and dispatches via the vtable. Used for overridden instance methods, which is the mechanism behind runtime polymorphism.

Parent p = new Child(); p.method() calls Child's version because the compiler generates invokevirtual Parent.method() (valid: Parent has the method). At runtime, the JVM checks p's actual object (Child), looks up Child's vtable, finds Child.method(), and calls it. The variable type (Parent) determined compile-time validity. The actual type (Child) determined runtime dispatch. This is why polymorphism works, and why static methods do not exhibit polymorphism (they use invokestatic which binds to the class at compile time, ignoring the actual object).

Remember one sentence: static binding resolves at compile time by reference type (private, static, final, overloaded), dynamic binding resolves at runtime by actual object type (overridden instance methods via vtable), and this is why p.method() calls Child's version when p holds a Child object.

---

Share
Use ← → arrow keys to navigate