Memorising encapsulation, abstraction, inheritance and polymorphism does not help when a question asks which method actually executes. You need a class-to-object mental model and a reliable way to trace a call. One Java payroll example supplies both: two employee objects, two pay formulas, one loop, and an output you can predict before running it.
Object-oriented programming starts with state, behaviour and identity
A class is a type-level blueprint. An object is one runtime instance with its own identity, state and behaviour. Employee can define the contract, while E1 and E2 are distinct objects. They remain distinct even if their fields temporarily contain equal values.
In a procedural design, a record may hold data while free functions operate on it. In an object-oriented design, an object can keep valid state behind methods and take responsibility for the operations that belong to it. OOP is therefore a way to assign responsibility, not merely syntax for putting functions inside a class.
If you would rather type these classes than read them, the Java module in Coding for Placements starts at JDK setup and works up through the language.
The four pillars of OOP solve four different design problems
Pillar | Design problem solved | Employee example |
|---|---|---|
Encapsulation | Protect state behind a controlled boundary | Keep |
Abstraction | Show the caller the needed operation, not its internal formula | Expose |
Inheritance | Let subtypes reuse and fulfil a common contract |
|
Polymorphism | Let one interface invoke different implementations | One |
Encapsulation and abstraction are often confused. Encapsulation controls access to state. Abstraction controls which idea the caller needs to see. A private field helps encapsulation, but it is not the whole of abstraction.
Subtype polymorphism means either concrete employee can be used wherever an Employee is expected. Inheritance is not the only kind of reuse, and languages do not all implement dispatch in exactly the same way.
Classes, objects and relationships: know what belongs to the type
A constructor establishes an object's initial state. Instance fields and methods belong to each object, while class-level or static members belong to the type. Access control limits what outside code can reach. A reference identifies an object that exists for some runtime lifetime.
In the example below, private final fields and the @Override annotation are Java syntax. The underlying ideas, protected state and a subtype implementation of a shared operation, are language-neutral.
Relationships must also match the model:
HourlyEmployeeis anEmployee, so it can satisfy the employee contract.Payrollhas aList<Employee>, so it should contain or refer to employees.Payroll extends Employeewould be wrong because payroll cannot behave as an employee.
Association, aggregation and composition describe different ownership strengths. A payroll service can refer to employees without owning their lifetime. A payslip line can be modelled as a part whose lifetime belongs to its payslip. This is a modelling distinction, not one universal memory-management rule for Java, C++ and Python.
OOP worked example: trace two employees through one abstract interface
Employee supplies one stable interface, while each concrete subtype owns its pay formula. Save all four classes in Payroll.java and run Payroll.
abstract class Employee {
private final String id;
Employee(String id) {
this.id = id;
}
String id() {
return id;
}
abstract int monthlyPay();
}
class SalariedEmployee extends Employee {
private final int annualSalary;
SalariedEmployee(String id, int annualSalary) {
super(id);
this.annualSalary = annualSalary;
}
@Override
int monthlyPay() {
return annualSalary / 12;
}
}
class HourlyEmployee extends Employee {
private final int hours;
private final int hourlyRate;
HourlyEmployee(String id, int hours, int hourlyRate) {
super(id);
this.hours = hours;
this.hourlyRate = hourlyRate;
}
@Override
int monthlyPay() {
return hours * hourlyRate;
}
}
public class Payroll {
public static void main(String[] args) {
Employee[] team = {
new SalariedEmployee("E1", 60000),
new HourlyEmployee("E2", 160, 30)
};
int total = 0;
for (Employee employee : team) {
int pay = employee.monthlyPay();
total += pay;
System.out.println(employee.id() + ": " + pay);
}
System.out.println("Total: " + total);
}
}Trace it step by step:
E1 is a
SalariedEmployee, so its override computes60000 / 12 = 5000pay units.E2 is an
HourlyEmployee, so its override computes160 * 30 = 4800pay units.The loop adds
5000 + 4800 = 9800pay units.
The output is exactly:
E1: 5000
E2: 4800
Total: 9800Private fields provide encapsulation. Abstract monthlyPay() provides abstraction. Both subclasses inherit the contract. The Employee[] loop exercises runtime polymorphism.

Overloading, overriding, static binding and dynamic binding are not synonyms
Overloading gives methods the same name but different parameter lists:
void show(int value) { }
void show(String value) { }
show(42); // selects show(int)
show("42"); // selects show(String)The argument's compile-time type determines the selected signature. This is normally discussed as static binding.
Overriding replaces an inherited instance-method implementation in a subtype:
Employee e = new HourlyEmployee("E2", 160, 30);
int pay = e.monthlyPay();The reference type exposes the Employee contract, but Java dispatches the overridden call to HourlyEmployee.monthlyPay(). It returns 160 * 30 = 4800.
The rule to carry into an exam is short: overload selection is static, overridden instance-method selection is dynamic. The specifics live at the boundary. Static, private and final methods, default arguments and multiple inheritance follow language-specific rules, so identify the language before applying any edge-case rule.

OOP traps: correct the model before tracing the code
Trap | Correction in the employee model |
|---|---|
Abstraction means private fields | Private fields encapsulate state; |
Inheritance represents has-a |
|
An overloaded signature is an overridden implementation | Overloading changes parameters; overriding supplies a subtype implementation |
The reference type decides the overridden target | Runtime type |
Equal fields mean the same object | Equal state does not erase the separate identities of E1 and E2 |
Java interfaces and garbage collection, C++ multiple inheritance and deterministic destructors, and Python duck typing change implementation details. The four design questions still transfer, but syntax-level answers must follow the language in the question.
How OOP questions test understanding, not definitions
Common questions ask you to classify a relationship, identify a valid declaration, separate overload from override, trace constructor or method calls, or predict output through a base-type reference. Interviews often add a design follow-up, such as when composition is safer than inheritance.
Use the worked model as a self-check: an Employee[] holds E1 and E2. Which implementations execute, and what total prints? A complete answer names SalariedEmployee.monthlyPay(), HourlyEmployee.monthlyPay() and 9800, not only the final number.
For the wider tour, including abstract classes versus interfaces, read Object Oriented Technology Explained: Four Pillars in Java. To decide how many hours OOP deserves beside the heavier subjects, use GATE CS Subject Weightage: Where Hours Pay Off.
Object-oriented programming: the short version and next step
Keep this five-point retrieval checklist:
An object combines identity, state and behaviour.
Encapsulation protects state behind a controlled boundary.
Abstraction exposes a stable contract.
Inheritance should model a genuine is-a relationship.
Dynamic dispatch selects an overridden implementation using runtime type.
Now add CommissionEmployee("E3", base = 2000, sales = 40000, rate = 5%). Its method should compute 0.05 * 40000 = 2000, then 2000 + 2000 = 4000 pay units. Without changing the payroll loop, the three-object total becomes 9800 + 4000 = 13800.
For runnable examples and question practice, continue with Java Course: Concepts, MCQs and Coding Questions. For a broader preparation route, use GATE Guidance by Sanchit Sir. The no-course alternative is equally concrete: implement the third subclass and explain every dispatch aloud.




