1. What is object-oriented programming?
Object-oriented programming (OOP) is a paradigm that organizes software around objects, which bundle data (fields) and behavior (methods) together. Programs are built from objects that interact by sending messages to each other. OOP aims to make code modular, reusable and easier to maintain through four pillars: encapsulation, abstraction, inheritance and polymorphism.
2. What is the difference between a class and an object?
A class is a blueprint that defines the fields and methods of a type, while an object is a concrete instance of that class created at runtime. The class exists once in code; you can create many objects from it, each with its own state. Objects share behavior through the class but hold separate data.
class Car {
constructor(public color: string) {}
drive() { return `${this.color} car moving`; }
}
const a = new Car("red");
const b = new Car("blue");
3. What are the four pillars of OOP?
The four pillars are encapsulation, abstraction, inheritance and polymorphism. Encapsulation hides internal state behind a controlled interface, abstraction exposes only essential features, inheritance lets a class reuse and extend another, and polymorphism lets one interface work with many concrete types.
4. What is encapsulation?
Encapsulation is bundling data with the methods that operate on it and restricting direct access to the internal state. Callers use public methods instead of touching fields, so the class can enforce invariants, validate input and change its internals without breaking users. It is usually achieved with private fields and getters or setters.
class Account {
#balance = 0;
deposit(n: number) {
if (n <= 0) throw new Error("Invalid amount");
this.#balance += n;
}
get balance() { return this.#balance; }
}
5. What is abstraction, and how is it different from encapsulation?
Abstraction means exposing only what an object does and hiding how it does it, while encapsulation is the mechanism of hiding internal data and protecting it with access control. Abstraction is a design-level idea solved with interfaces and abstract classes; encapsulation is an implementation-level technique solved with access modifiers. A Car offers start() (abstraction) while keeping its engine fields private (encapsulation).
6. What is inheritance and what are its types?
Inheritance lets a child class acquire the fields and methods of a parent class, modelling an "is-a" relationship and enabling code reuse. Common forms are single, multilevel, hierarchical and (in languages such as C++ and Python) multiple inheritance. Java and C# allow only single class inheritance but permit implementing multiple interfaces.
class Animal { eat() { return "eating"; } }
class Dog extends Animal { bark() { return "woof"; } }
new Dog().eat();
7. What is polymorphism and what are its types?
Polymorphism means one interface or method name can behave differently depending on the object it is used with. Compile-time (static) polymorphism comes from method overloading and operator overloading, while runtime (dynamic) polymorphism comes from method overriding, where the actual object type decides which method runs. It lets you write code against a base type that works with any subtype.
abstract class Shape { abstract area(): number; }
class Circle extends Shape { constructor(private r: number) { super(); } area() { return Math.PI * this.r ** 2; } }
class Square extends Shape { constructor(private s: number) { super(); } area() { return this.s ** 2; } }
const shapes: Shape[] = [new Circle(1), new Square(2)];
shapes.map(s => s.area());
8. What is the difference between method overloading and method overriding?
Overloading defines several methods with the same name but different parameter lists in the same class and is resolved at compile time, while overriding redefines a parent method with the same signature in a subclass and is resolved at runtime. Overloading cannot be done by return type alone. Overriding requires an inheritance relationship and enables dynamic polymorphism.
9. What is the difference between an abstract class and an interface?
An abstract class can hold state, constructors and both implemented and abstract methods, and a class can extend only one of them in most languages. An interface defines a contract of method signatures with no instance state, and a class can implement many interfaces. Use an abstract class to share code among closely related types and an interface to define a capability that unrelated types can provide. Modern Java and C# interfaces may include default methods, which blurs the line, but they still cannot hold instance fields.
10. What is a constructor, and what types are there?
A constructor is a special method that runs when an object is created and initializes its state. Common types are the default (no-argument) constructor, parameterized constructor and copy constructor. If you define no constructor, many languages such as Java and C# supply a default one automatically. Constructors have no return type and carry the class name, except in languages like JavaScript or Python where they are named constructor or __init__.
class User {
constructor(public name: string, public age = 18) {}
}
new User("Ana");
11. What are access modifiers?
Access modifiers control which code can see and use a class member. The usual ones are public (everywhere), private (inside the same class only), protected (the class and its subclasses) and, in Java, package-private by default. They support encapsulation by limiting the public surface of a class so internals can change safely.
12. What does the static keyword mean?
A static member belongs to the class itself rather than to any instance, so it is shared by all objects and can be accessed without creating one. Static methods cannot use this or instance fields directly. Typical uses are utility functions, constants and counters shared across instances.
class Counter {
static count = 0;
constructor() { Counter.count++; }
}
new Counter(); new Counter();
console.log(Counter.count);
13. What is the difference between composition and inheritance?
Inheritance models an "is-a" relationship by extending a parent class, while composition models "has-a" by building a class from other objects it holds and delegates to. Composition is more flexible because behavior can be swapped at runtime and it avoids tight coupling to a parent's internals. This is why the common guideline is to favor composition over inheritance.
class Engine { start() { return "vroom"; } }
class Car {
constructor(private engine: Engine) {}
start() { return this.engine.start(); }
}
14. What is the difference between aggregation, composition and association?
Association is a general "uses" relationship between independent objects. Aggregation is a weak "has-a" where the part can exist without the whole, such as a team and its players. Composition is a strong "has-a" where the part's lifetime is owned by the whole, such as a house and its rooms, so destroying the whole destroys the parts.
15. What are coupling and cohesion?
Coupling is how strongly one module depends on another, and cohesion is how closely the responsibilities inside a single module belong together. Good design aims for low coupling and high cohesion, so classes can change independently and each does one well-defined job. Interfaces and dependency injection are common ways to reduce coupling.
16. What are the SOLID principles?
SOLID is a set of five design principles for maintainable object-oriented code: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation and Dependency Inversion. Together they promote small focused classes, extension without modification, safe substitution of subtypes, narrow interfaces and dependence on abstractions instead of concrete classes.
17. What is the Single Responsibility Principle?
The Single Responsibility Principle says a class should have only one reason to change, meaning it should own one responsibility. A class that both calculates invoices and writes them to a database must change for two unrelated reasons. Splitting these into separate classes makes each easier to test, understand and modify.
18. What is the Open/Closed Principle?
The Open/Closed Principle states that software entities should be open for extension but closed for modification. You add new behavior by writing new code, such as a new subclass or strategy, instead of editing tested existing code. It is usually achieved through abstractions and polymorphism rather than long if/else or switch chains.
interface Discount { apply(total: number): number; }
class Seasonal implements Discount { apply(t: number) { return t * 0.9; } }
function checkout(total: number, d: Discount) { return d.apply(total); }
19. What is the Liskov Substitution Principle?
The Liskov Substitution Principle says that objects of a subclass must be usable wherever the parent type is expected without breaking correctness. A subclass should not strengthen preconditions, weaken postconditions or throw unexpected exceptions. The classic violation is a Square extending Rectangle, where setting width alone unexpectedly changes height.
20. What are the Interface Segregation and Dependency Inversion principles?
Interface Segregation says clients should not be forced to depend on methods they do not use, so prefer several small interfaces over one large one. Dependency Inversion says high-level modules should depend on abstractions, not on concrete low-level classes, and both should depend on interfaces. Dependency injection is the usual technique for applying Dependency Inversion.
21. What is the Singleton pattern?
The Singleton pattern ensures a class has exactly one instance and provides a global access point to it. It is implemented with a private constructor and a static accessor, and in multithreaded languages creation must be thread-safe. It is often criticized because it introduces global state and hurts testability, so dependency injection is usually preferred.
class Config {
private static instance: Config;
private constructor() {}
static get(): Config {
return (Config.instance ??= new Config());
}
}
22. What is the Factory pattern?
The Factory pattern delegates object creation to a dedicated method or class so callers do not depend on concrete classes. The caller asks for a type by parameter and receives an object that implements a common interface. This keeps creation logic in one place and makes it easy to add new types.
interface Notifier { send(m: string): void; }
class Email implements Notifier { send(m: string) {} }
class Sms implements Notifier { send(m: string) {} }
function createNotifier(kind: "email" | "sms"): Notifier {
return kind === "email" ? new Email() : new Sms();
}
23. What is the Observer pattern?
The Observer pattern defines a one-to-many dependency where a subject notifies all its registered observers automatically when its state changes. Observers subscribe and unsubscribe at runtime, so the subject does not need to know their concrete types. It underlies event listeners, publish-subscribe systems and reactive UI frameworks.
class Subject {
private subs: Array<(v: number) => void> = [];
subscribe(fn: (v: number) => void) { this.subs.push(fn); }
emit(v: number) { this.subs.forEach(fn => fn(v)); }
}
24. How does the compiler or runtime decide which method to call (static vs dynamic binding)?
Static (early) binding resolves the method at compile time and applies to overloaded, private, static and final methods. Dynamic (late) binding resolves the method at runtime from the actual object type and applies to overridden instance methods through a virtual dispatch table. This is how a variable of a parent type can run a subclass's override.
25. What is an immutable object and why is it useful?
An immutable object cannot change its state after creation; any modification produces a new object instead. Immutability makes objects inherently thread-safe, easier to reason about and safe to share or use as map keys. Typical examples are Java String and JavaScript primitives, and you can model it with readonly fields and Object.freeze.
class Point {
constructor(readonly x: number, readonly y: number) {}
withX(x: number) { return new Point(x, this.y); }
}
26. What is the Strategy pattern and when would you use it?
The Strategy pattern defines a family of interchangeable algorithms behind a common interface so the caller can pick or swap one at runtime. Use it when a class contains if/else or switch logic that chooses between behaviors such as pricing rules, sorting or payment methods. It supports the Open/Closed Principle because adding a new algorithm means adding a class, not editing the context.
interface Pay { pay(amount: number): string; }
class Card implements Pay { pay(a: number) { return `card ${a}`; } }
class Upi implements Pay { pay(a: number) { return `upi ${a}`; } }
class Checkout {
constructor(private method: Pay) {}
run(a: number) { return this.method.pay(a); }
}
27. What is the Decorator pattern, and how is it different from inheritance?
The Decorator pattern wraps an object in another object with the same interface to add behavior dynamically, without changing the original class. Unlike inheritance, which fixes behavior at compile time and can explode into many subclasses, decorators can be stacked and combined at runtime. Java's BufferedReader wrapping a FileReader and middleware chains are familiar examples.
interface Coffee { cost(): number; }
class Plain implements Coffee { cost() { return 3; } }
class Milk implements Coffee {
constructor(private inner: Coffee) {}
cost() { return this.inner.cost() + 1; }
}
new Milk(new Plain()).cost();
28. What is the Adapter pattern?
The Adapter pattern converts the interface of an existing class into the interface that clients expect, so incompatible classes can work together. It wraps the incompatible object and translates calls. It is common when integrating a third-party library or legacy code without modifying it, for example wrapping several payment SDKs behind one PaymentGateway interface.
29. What is the Builder pattern and what problem does it solve?
The Builder pattern constructs a complex object step by step, separating the construction process from the final representation. It solves the telescoping constructor problem, where a class has many optional parameters and long, error-prone constructor signatures. A fluent builder gives readable, named configuration and can validate everything in a final build() call.
class QueryBuilder {
private parts: string[] = [];
from(t: string) { this.parts.push(`FROM ${t}`); return this; }
where(c: string) { this.parts.push(`WHERE ${c}`); return this; }
build() { return this.parts.join(" "); }
}
30. What is the difference between the Factory Method and Abstract Factory patterns?
Factory Method uses a single method, usually overridden by subclasses, to create one kind of product. Abstract Factory is an interface for creating whole families of related objects that must be used together, such as a UI toolkit producing matching buttons and checkboxes for each operating system. Factory Method relies on inheritance, while Abstract Factory relies on composition and is heavier but guarantees consistent product families.
31. What is the Command pattern and where is it used?
The Command pattern encapsulates a request as an object, so it can be queued, logged, passed around, executed later or undone. Each command exposes something like execute() and optionally undo(). It is the classic way to implement undo/redo in editors, task queues, macro recording and transactional operations.
interface Command { execute(): void; undo(): void; }
class AddText implements Command {
constructor(private doc: string[], private t: string) {}
execute() { this.doc.push(this.t); }
undo() { this.doc.pop(); }
}
32. What is the difference between the Proxy and Decorator patterns?
Both wrap an object with the same interface, but their intent differs. A Decorator adds new responsibilities to the wrapped object, while a Proxy controls access to it, for example through lazy loading, caching, access checks or remote calls. A proxy usually manages the real object's lifecycle itself, whereas decorators are composed by the client.
33. What is the Template Method pattern?
The Template Method pattern defines the skeleton of an algorithm in a base class and lets subclasses override specific steps without changing the overall structure. The base method fixes the order of steps, while abstract or hook methods supply the variable parts. It is useful for processes like data import pipelines, and it follows the "Hollywood principle": do not call us, we will call you.
abstract class Report {
generate() { return this.header() + this.body(); }
protected header() { return "Report\n"; }
protected abstract body(): string;
}
34. What is the difference between the Strategy and State patterns?
Both delegate behavior to a swappable object, but Strategy is chosen by the client and strategies usually do not know about each other. In State, the object changes its own behavior as its internal state changes, and the state objects themselves often trigger transitions to other states. Use State for a lifecycle such as an order moving from pending to shipped, and Strategy for selecting an algorithm.
35. What is the fragile base class problem?
The fragile base class problem occurs when a seemingly safe change to a parent class breaks its subclasses, because subclasses depend on the parent's internal behavior. A classic case is a subclass overriding add() and addAll() on a collection that calls add() internally, causing double counting. It is a main reason to favor composition, mark classes final or sealed when not designed for extension, and document overridable methods.
36. What is the Law of Demeter?
The Law of Demeter, or principle of least knowledge, says an object should only talk to its immediate collaborators and not reach through them to strangers. A call like order.getCustomer().getAddress().getCity() couples the caller to the whole chain. Instead, ask the nearest object to do the work, such as order.shippingCity(), which reduces coupling and makes refactoring easier.
37. What is the "Tell, Don't Ask" principle?
Tell, Don't Ask means you should tell an object what to do instead of asking for its data and making decisions outside it. Pulling state out through getters and then applying logic in the caller leaks the object's responsibilities and weakens encapsulation. Moving that logic into the owning class keeps behavior next to the data it uses.
acct.withdraw(amt);
38. What is an anemic domain model, and why is it considered an anti-pattern?
An anemic domain model is a set of classes that only hold data with getters and setters, while all business logic lives in separate service classes. It is criticized because it breaks encapsulation, spreads invariants across many places and reduces objects to data structures, which is closer to procedural code. A rich domain model keeps rules inside the entities that own the data, though simple CRUD apps may not need that.
39. What is the God Object anti-pattern and how do you refactor it?
A God Object is a class that knows too much or does too much, violating the Single Responsibility Principle and becoming hard to test and change. To refactor, identify distinct responsibilities, extract each into its own cohesive class, and have the original delegate to them. Do this in small, test-protected steps, using interfaces to keep dependencies low.
40. What are dependency injection and inversion of control, and how do they differ?
Inversion of Control is the broad principle that a framework or container controls the flow and object creation instead of your code doing it. Dependency injection is one specific technique for it, where a class receives its dependencies from outside, through the constructor, a setter or a method, instead of creating them. Constructor injection is preferred because dependencies are explicit and the object is never half-built.
class OrderService {
constructor(private repo: OrderRepo, private mailer: Mailer) {}
}
41. How does dependency injection make code easier to test?
Dependency injection lets tests pass fake or mock implementations of collaborators instead of real databases, networks or clocks. Because the class depends on an interface, you can verify its logic in isolation, quickly and deterministically. Without injection, hard-coded new calls and singletons tie the class to real infrastructure and force slow integration tests.
42. Why does the "Square extends Rectangle" example break the Liskov Substitution Principle, and how would you fix the design?
It breaks because a rectangle's contract lets width and height vary independently, but a square must keep them equal, so code that sets width and expects the area to be width * height fails with a square. The fix is not to model the "is-a" relationship from mathematics but from behavior: make both implement a common Shape interface with an area() method, or make them immutable so no setter contradicts the other.
43. When would you choose an abstract class over an interface in a real design?
Choose an abstract class when related subclasses share state or a substantial default implementation, or when you want a template method that fixes an algorithm's structure. Choose an interface when unrelated types must provide the same capability or when you want callers to depend on a minimal contract. In practice, define the interface for consumers and optionally offer an abstract base class that implements it to reduce boilerplate.
44. What is the Repository pattern, and why use it?
The Repository pattern hides data access behind a collection-like interface such as findById and save, so domain code does not know whether data comes from SQL, an API or memory. It decouples business logic from persistence, supports Dependency Inversion and allows an in-memory fake in tests. The trade-off is an extra abstraction layer, which adds little value in very small apps.
interface UserRepo {
findById(id: string): Promise<User | null>;
save(u: User): Promise<void>;
}
45. What is the difference between shallow copy and deep copy of an object?
A shallow copy duplicates the object but its nested objects are still shared references, while a deep copy recursively duplicates everything so the copy is fully independent. Mutating a nested field in a shallow copy also changes the original, which causes subtle bugs. In JavaScript, spread and Object.assign are shallow, and structuredClone makes a deep copy.
const a = { tags: ["x"] };
const shallow = { ...a };
shallow.tags.push("y");
const deep = structuredClone(a);
46. What are the equals and hashCode contract rules, and what goes wrong if you break them?
If two objects are equal according to equals, they must return the same hashCode, and equals must be reflexive, symmetric, transitive and consistent. Unequal objects may share a hash code. If you override equals but not hashCode in Java, hash-based collections such as HashMap and HashSet can fail to find an object you just stored, because it lands in a different bucket.
47. How would you design a parking lot system using OOP?
Start by identifying the nouns as classes: ParkingLot, Level, Spot, Vehicle, Ticket and Payment. Make Vehicle abstract with subclasses like Car and Motorcycle, and give Spot a type so the lot can find a compatible free spot. Put the fee calculation behind a Strategy interface so hourly or flat pricing can change without editing existing code, and keep rules such as capacity inside the class that owns that data.
abstract class Vehicle { abstract readonly size: "S" | "M" | "L"; }
interface FeePolicy { fee(minutes: number): number; }
class Ticket { constructor(readonly spotId: string, readonly start: Date) {} }
48. How do you handle exceptions in an OOP design, and what are good practices?
Throw exceptions only for exceptional conditions, not normal control flow, and throw the most specific type that carries useful context. Catch an exception only where you can handle or add meaning to it, and never swallow errors silently. Wrap low-level exceptions into domain exceptions at layer boundaries so callers are not coupled to implementation details, and use finally or language features such as using or try-with-resources to release resources.
49. What is the difference between a value object and an entity in domain modelling?
An entity has a unique identity that persists over time, so two entities with the same attribute values are still different if their IDs differ, such as a User. A value object has no identity and is defined only by its attributes, so two with equal values are interchangeable, such as Money or Address. Value objects should be immutable, which makes them safe to share and simple to compare.
50. What does "program to an interface, not an implementation" mean?
It means code should depend on abstract types, such as interfaces, instead of concrete classes, so the implementation can be replaced without changing the callers. For example, declare a variable as Notifier instead of EmailNotifier, or List instead of ArrayList in Java. This lowers coupling, enables polymorphism and testing with fakes, and is the foundation of the Dependency Inversion Principle.