Skip to content

Keywords & Modifiers Interview Questions & Answers

22 questions Updated 2026-06-20 Share:

Java keywords and modifiers interview questions — access modifiers, static, final, abstract, the static vs instance distinction, transient and volatile, synchronized, and other reserved keywords.

Read the in-depth guideJava Keywords & Modifiers — Access, static, final & abstract Explained(opens in new tab)
22 of 22

Access modifiers control visibility — who can see a class, field, method, or constructor. There are four levels, from most to least restrictive:

Modifier Same class Same package Subclass (other pkg) Everywhere
private yes no no no
(default) yes yes no no
protected yes yes yes no
public yes yes yes yes
public class Account {
  private double balance;   // only this class
  String owner;             // package-private (no keyword)
  protected int id;         // package + subclasses
  public String getOwner() { return owner; } // anyone
}

"Default" (also called package-private) is what you get with no keyword — it is not a keyword itself. The guiding principle: declare everything as private as you can and widen only when there's a real need.

private restricts a member to its own class — not even subclasses can see it. protected widens that to the same package plus any subclass, including subclasses in a different package (the one case package-private doesn't cover).

package base;
public class Animal {
  private String dna;       // hidden from everyone but Animal
  protected String species; // visible to subclasses & same package
}

package zoo;
class Dog extends Animal {
  void show() {
    // System.out.println(dna);     // won't compile — private
    System.out.println(species);     // OK — protected, inherited
  }
}

Use private for true implementation detail and protected only when you intend a member to be part of the inheritance contract.

static binds a member to the class itself rather than to any instance. There is exactly one copy shared by all objects, and you access it through the class name — no new required. It applies to fields, methods, nested classes, and initializer blocks.

class MathUtil {
  static final double PI = 3.14159;   // one shared constant
  static int square(int n) {          // call without an instance
    return n * n;
  }
}
MathUtil.square(5);   // 25 — no object created
MathUtil.PI;          // shared field

A static method can't use this or access instance fields directly, because there's no particular object in play. Statics load with the class and live for the program's lifetime.

A static member belongs to the class — one shared copy for the whole program. An instance member belongs to each object — every new gets its own copy.

class Counter {
  static int total;   // one shared across all Counters
  int id;             // one per instance
  Counter() { id = ++total; }
}
new Counter(); // id=1, total=1
new Counter(); // id=2, total=2  (total is shared)

Static methods can only touch static state; instance methods can touch both. You can call a static member before any object exists, whereas instance members require an object. Mutable static state is shared globally, which makes it a common source of threading bugs and memory leaks.

A static nested class is a class declared static inside another; it does not hold a reference to an enclosing instance and can be created on its own. A non-static nested class (an inner class) is tied to an outer instance and can access its members.

class Outer {
  int x = 10;
  static class Nested {        // no link to an Outer instance
    int sum(int a) { return a + 5; }
  }
  class Inner {                // bound to an Outer instance
    int read() { return x; }   // can use outer's x
  }
}
Outer.Nested n = new Outer.Nested();        // no Outer needed
Outer.Inner i = new Outer().new Inner();    // needs an Outer

Prefer static nested unless the class genuinely needs the outer instance — inner classes silently retain the enclosing object, a frequent memory-leak cause.

A final variable can be assigned only once. For a primitive that locks the value; for a reference it locks which object the variable points to — the object itself can still be mutated.

final int MAX = 10;     // MAX = 11; would not compile
final List<String> xs = new ArrayList<>();
xs.add("ok");           // mutating the object is allowed
xs = new ArrayList<>(); // reassigning the reference is not

final applies to local variables, fields, and method parameters, and it's required (or "effectively final" status is) for any local variable captured by a lambda or anonymous class.

A final method can't be overridden by a subclass — it fixes the behavior. A final class can't be extended at all (e.g. String, Integer). Both are tools for protecting invariants and enabling optimization.

class Base {
  final void audit() { }    // subclasses cannot override this
}
final class Money { }       // cannot be subclassed
// class Cash extends Money {}  // compile error

Making a class final is a common way to guarantee immutability stays intact (no subclass can add mutable state or override behavior), and it lets the compiler/JIT inline final methods more aggressively.

A blank final is a final field declared without an initializer; it must then be assigned exactly once in every constructor (or an instance initializer) before construction completes. A final parameter simply can't be reassigned inside the method body.

class Point {
  final int x;             // blank final
  Point(int x) { this.x = x; }   // must be set here

  int shift(final int by) {
    // by = by + 1;        // illegal — final parameter
    return x + by;
  }
}

Blank finals let you set an immutable field from constructor arguments rather than a fixed literal — the backbone of immutable classes. Static blank finals must be assigned in a static block.

No. final only freezes the reference, not the object's contents. A final variable can't be reassigned, but if the object it points to is mutable, its state can still change.

final StringBuilder sb = new StringBuilder("a");
sb.append("b");        // allowed — object is mutated
// sb = new StringBuilder(); // not allowed — reference is final

Immutability is a stronger, design-level property: it requires all fields be final and private, no setters, defensive copies of mutable inputs/outputs, and usually a final class. final is one ingredient of immutability, not the whole recipe.

The idiom is static final with an UPPER_SNAKE_CASE name. static gives one shared copy; final makes it unassignable. The compiler can inline static final primitive and String constants for efficiency.

public class Config {
  public static final int MAX_RETRIES = 3;
  public static final String APP_NAME = "Interviews";
}

Remember final on a reference only freezes the reference — a static final List<String> X = new ArrayList<>() can still be mutated. Use List.of(...) or Collections.unmodifiableList for a genuinely constant collection.

An abstract method declares a signature with no body — subclasses must implement it. An abstract class can't be instantiated; it exists to be extended and may mix abstract methods with concrete ones and state.

abstract class Shape {
  abstract double area();        // no body — subclass must provide it
  void describe() {              // concrete method is allowed
    System.out.println("area=" + area());
  }
}
class Circle extends Shape {
  double r;
  double area() { return Math.PI * r * r; }  // implementation
}
// new Shape();  // compile error — abstract class

Any class with even one abstract method must be declared abstract. Use it when you want shared code plus mandatory hooks for subclasses to fill in.

abstract means "must be overridden by a subclass." The conflicting modifiers all prevent overriding, so the combinations are contradictory and rejected at compile time:

Combination Why it's illegal
abstract final final forbids overriding; abstract requires it
abstract static static methods aren't polymorphic / can't be overridden
abstract private private isn't inherited, so it can't be overridden
abstract synchronized nothing to lock — there's no body
abstract native native has an (external) body; abstract has none
abstract class C {
  // abstract final void a();   // error
  // abstract static void b();  // error
  // abstract private void c(); // error
}

The rule of thumb: abstract is only compatible with public/protected (and package-private) — anything that keeps the method open to overriding.

transient marks an instance field to be skipped during serialization. When a Serializable object is written out, transient fields are ignored; on deserialization they come back as their default (0, false, null).

class Session implements Serializable {
  String user;                 // serialized
  transient String authToken;  // NOT serialized — restored as null
  transient int cacheSize;     // restored as 0
}

Use it for sensitive data (passwords, tokens), values you can recompute, or non-serializable fields you don't want to persist. transient has no effect on static fields (statics aren't part of an instance's serialized state anyway).

volatile tells the JVM a field may be changed by multiple threads, so every read goes to main memory and every write is immediately visible to other threads — no caching in a thread-local register. It also establishes a happens-before ordering, preventing certain instruction reorderings.

class Worker {
  private volatile boolean running = true;  // visibility guaranteed
  void stop() { running = false; }          // seen by the run() thread
  void run() {
    while (running) { /* ... */ }           // won't loop forever
  }
}

What it does not give you is atomicity of compound actions: volatile int x; x++; is still a race (read-modify-write isn't atomic). For that you need synchronized or the Atomic* classes. Use volatile for simple flags and the safe-publication of a single value.

synchronized provides mutual exclusion: only one thread at a time can hold a given object's monitor lock, so a synchronized block/method runs without interference. It also gives visibility guarantees (a happens-before edge on lock release/acquire).

class Counter {
  private int count;
  synchronized void inc() { count++; }   // locks on 'this'

  private final Object lock = new Object();
  void update() {
    synchronized (lock) {                // locks on a private monitor
      count += 2;
    }
  }
}

A synchronized method locks this (or the Class object for a static method); a synchronized block locks the object you name. Prefer a private lock object over locking this to avoid outside code interfering with your lock.

this is a reference to the current object. Its three common uses: disambiguating a field from a same-named parameter, passing the current object to another method, and calling another constructor of the same class (this(...)).

class Point {
  int x, y;
  Point(int x, int y) {
    this.x = x;          // field vs parameter
    this.y = y;
  }
  Point() {
    this(0, 0);          // constructor chaining — must be first statement
  }
  Point self() { return this; } // return the current object
}

this is unavailable in a static context — there's no current instance. The this(...) constructor call, if used, must be the first statement.

super refers to the parent class. It does two jobs: calling a superclass constructor (super(...)) and accessing an overridden method or hidden field of the parent (super.method()).

class Animal {
  String sound() { return "..."; }
  Animal(String name) { /* ... */ }
}
class Dog extends Animal {
  Dog() {
    super("Rex");                 // parent constructor — must be first
  }
  @Override String sound() {
    return super.sound() + "woof"; // call the parent's version
  }
}

If you don't write super(...), the compiler inserts an implicit no-arg super() — which is why a parent with only a parameterized constructor forces every subclass to call super(...) explicitly.

instanceof tests whether an object is an instance of a given type (or a subtype), returning a boolean. Since Java 16, pattern matching lets you bind the result to a typed variable in one step, removing the manual cast.

Object o = "hello";

if (o instanceof String) {            // classic form
  String s = (String) o;              // explicit cast needed
  System.out.println(s.length());
}

if (o instanceof String s) {          // pattern matching (Java 16+)
  System.out.println(s.length());     // 's' is already a String
}

Key facts: instanceof on null is always false (so it's a built-in null guard), and it's the standard way to make a downcast safe before performing it.

Both are rarely written but show up in interviews. native marks a method implemented in non-Java code (typically C/C++) via the JNI — it has no Java body. strictfp forces floating-point math to follow strict IEEE-754 rules for fully portable, reproducible results across platforms.

class Bridge {
  native void readSensor();   // body lives in a native library, no { }
}

strictfp class Calc {         // all FP ops here are platform-independent
  double scale(double x) { return x * 1.1; }
}

native is how the JDK itself reaches OS-level features. strictfp mattered because older JVMs could use wider intermediate precision; as of Java 17 all floating-point is strict by default, so the keyword is now effectively a no-op.

Two words are reserved keywords — so you can't use them as identifiers — yet the language assigns them no function: goto and const.

// int goto = 5;   // compile error — reserved word
// int const = 1;  // compile error — reserved word

They were reserved deliberately so that programmers coming from C/C++ wouldn't accidentally use them and so the language could repurpose them later (it never did). Java uses final instead of const, and structured control flow plus labeled break/continue instead of goto.

No — var (Java 10+) is a reserved type name, not a true keyword. That distinction matters: because it isn't a keyword, you can still legally use var as a variable, method, or package name (just not as a class name). It provides local variable type inference: the compiler infers the static type from the initializer.

var list = new ArrayList<String>();  // inferred ArrayList<String>
var count = 10;                      // int — still statically typed

int var = 5;        // legal! 'var' as an identifier
// var var = 5;     // illegal — can't infer here

Restrictions: local variables with an initializer only — never fields, method parameters, return types, or var x; / var x = null;. It's syntactic sugar, not dynamic typing.

Many modifiers stack freely (public static final), but some combinations are contradictory and rejected by the compiler. The conflicts cluster around abstract (which requires overriding) and the modifiers that forbid it.

Combination Legal? Reason
public static final yes the classic constant
private static yes class-scoped helper
abstract final no one demands overriding, the other forbids it
abstract static no static methods can't be overridden
abstract private no private isn't inherited
final abstract (class) no a final class can't be subclassed/extended
two access modifiers no public private int x; is meaningless
public static final int LIMIT = 5;   // fine
// abstract final void f();          // error — see above

Rule of thumb: a member can have at most one access modifier, and abstract is incompatible with anything that closes the door to overriding.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel