Fundamentals · Object-oriented programming
Object-oriented programming: the questions candidates report
- 18 questions
- 5 themes
- 14 companies
- 17 with candidate reports
Object-oriented programming questions reported by candidates in campus and experienced hiring loops, from the four pillars to SOLID, virtual dispatch, memory and design rounds. Read each answer, then say it aloud with your own example.
The four pillars
What are the four pillars of object-oriented programming? Explain each with a real-world example.CoreSeveral reports
The four pillars are encapsulation, abstraction, inheritance and polymorphism.
- Encapsulation: keep data and the methods that use it together, and hide the fields behind a controlled interface. A
BankAccountkeepsbalanceprivate and changes it only throughdepositandwithdraw, which enforce the rules. - Abstraction: expose what an object does, not how. A caller uses
PaymentGateway.charge()without knowing which provider sits behind it. - Inheritance: a subclass reuses and extends a base class, modelling an is-a relationship such as
SavingsAccountandAccount. - Polymorphism: one call, many behaviours, chosen by the object's actual type.
The usual follow-ups are how encapsulation differs from abstraction and when composition beats inheritance.
- Encapsulation: keep data and the methods that use it together, and hide the fields behind a controlled interface. A
How is encapsulation different from abstraction?CoreSeveral reports
Both hide something, but they work at different levels and answer different problems.
- Encapsulation is an implementation mechanism. It binds state and behaviour into one unit and restricts direct access with access modifiers, so an object can protect its own invariants. It hides data.
- Abstraction is a design idea. It exposes only the essential operations and hides the complexity behind them, using interfaces and abstract classes. It hides implementation.
Example: a
Stackinterface withpushandpopis abstraction, because callers do not care whether an array or a linked list is used. A concrete stack with a private array and an index thatpushbounds-checks is encapsulation.Adding a getter and a setter for every private field is not real encapsulation, since the state is still open to anyone. Interviewers often ask this next.
What is the difference between an abstract class and an interface, and when would you pick each?CoreSeveral reports
An abstract class is a partially built base class. It can hold fields, constructors and concrete methods next to abstract ones, and a class can extend only one in Java or C#. An interface is a contract of behaviour. A class can implement many, and traditionally it holds no instance state. Java 8 added
defaultandstaticmethods to interfaces.- Choose an abstract class when closely related types share state and code, as in a template method where the base class fixes the steps.
- Choose an interface for a capability that unrelated classes can offer, such as
ComparableorRunnable, or when you want callers to depend on a contract only.
Example: an interface
PaymentMethodwith an abstract classBaseCardthat shares validation. C++ has no interface keyword; a class with only pure virtual functions plays that role. A common follow-up is whether an abstract class can have a constructor. It can, and a subclass constructor calls it.
Polymorphism and dispatch
What is polymorphism? Compare compile-time and run-time polymorphism with examples.CoreSeveral reports
Polymorphism means one interface with many behaviours: the same call does different work depending on the types involved.
- Compile-time (static) polymorphism is resolved by the compiler. Examples are overloading, such as
area(int r)andarea(int w, int h), operator overloading and C++ templates. - Run-time (dynamic) polymorphism is resolved while the program runs. A base-type reference calls an overridden method, so
shape.draw()runs theCircleversion when the object is aCircle.
Run-time polymorphism needs inheritance or an interface and, in C++, a
virtualfunction. In Java, instance methods that are not private, static or final are dispatched dynamically by default.Expect the follow-up on how dynamic dispatch works, which is normally a virtual table lookup, and why it costs slightly more than a direct call.
- Compile-time (static) polymorphism is resolved by the compiler. Examples are overloading, such as
What is the difference between method overloading and method overriding?CoreSeveral reports
Overloading means several methods with the same name but different parameter lists in the same scope. The compiler picks one from the static argument types, so it is compile-time polymorphism. A different return type alone is not enough to overload.
Overriding means a subclass supplies its own body for an inherited method with the same signature. The version that runs is chosen from the object's real type at run time. In Java the return type may be covariant and the access cannot be narrower; mark it with
@Override. In C++ the base method must bevirtual, andoverridecatches mistakes.- Pitfall: with
print(Object)andprint(String), a call through anObject-typed variable holding a string still picksprint(Object), because overloads use the static type. - Static methods are hidden, not overridden.
- Pitfall: with
What are virtual and pure virtual functions in C++, and how does the vtable make them work?DeepSeveral reports
A virtual function is declared in a base class so that a call through a base pointer or reference runs the override of the object's real type. A pure virtual function, written
virtual void draw() = 0;, has no required body. A class containing one is abstract and cannot be instantiated, and a derived class must override it to become concrete.Typical implementation: every class with virtual functions gets a vtable, a table of function pointers. Each object stores a hidden vptr to its class's table. A virtual call loads the vptr, indexes the table and jumps indirectly. That costs an extra memory load and usually prevents inlining.
- Make the base destructor virtual; deleting a derived object through a base pointer without it has no defined behaviour.
- A constructor cannot be virtual, because the object's real type is still being established while it is built.
The follow-up is usually which function a virtual call inside a constructor reaches. It reaches the version of the class being constructed.
What kinds of inheritance exist, what is the diamond problem, and in what order do constructors and destructors run?DeepSeveral reports
Kinds: single, multilevel, multiple, hierarchical and hybrid. Java allows multiple inheritance only through interfaces.
The diamond problem:
Dinherits fromBandC, and both inherit fromA. In C++ that givesDtwo copies ofA, sod.xis ambiguous. Declareclass B : virtual public A(and the same forC), andDholds one sharedA, which the most derived class constructs. In Java, if two interfaces give conflicting default methods, the class must override and choose.- In C++, constructors run base first, then member objects, then the derived body. Multiple bases run in declaration order.
- Destructors run in exactly the reverse order.
Example:
Cderived fromBderived fromAprints A, B, C on construction and C, B, A on destruction. A common trap is guessing output when the base destructor is not virtual.
SOLID, design and patterns
What are the SOLID principles? Give a short example for each.CoreSeveral reports
SOLID is five guidelines for keeping class designs easy to change.
- Single responsibility: a class has one reason to change. Split
InvoicefromInvoicePrinter. - Open/closed: open to extension, closed to modification. Add a new
DiscountRuleclass instead of editing anifchain. - Liskov substitution: a subtype must work wherever its base type is expected. A
Squarethat breaks theRectanglecontract that width and height vary separately violates it. - Interface segregation: prefer small, focused interfaces so clients do not depend on methods they never use.
- Dependency inversion: high-level code depends on abstractions, not concrete classes. An
OrderServicetakes aPaymentGatewayinterface through its constructor.
Interviewers often ask you to spot which principle a given snippet breaks, or to refactor it.
- Single responsibility: a class has one reason to change. Split
Why is composition often preferred over inheritance?CoreOne report
Inheritance models is-a and gives code reuse plus polymorphism. Composition models has-a: an object holds a reference to a collaborator and delegates to it.
Inheritance couples a subclass tightly to its parent's internals, so a change in the base class can silently break children (the fragile base class problem). The relationship is also fixed at compile time and uses up the single parent slot in languages such as Java.
Composition keeps classes small, lets you swap behaviour at run time, and makes testing easier because you can pass in a fake.
Example: a
Penguinthat extendsFlyingBirdmust disable flying. Instead giveBirdaMoveBehaviour, and injectWalkorFly. That is the strategy pattern.Use inheritance only when the child can truly substitute for the parent (Liskov), and the follow-up is often to name a case where inheritance is the right call.
How do you approach an object-oriented design problem such as a parking lot, cab booking or vehicle rental?DeepSeveral reports
Work in a fixed order so the design stays defensible under follow-ups.
- Clarify requirements and scope: vehicle types, pricing, payments, how many floors, what the caller can do.
- Take nouns as entities (
ParkingLot,Floor,Spot,Vehicle,Ticket) and verbs as behaviours (park,unpark,pay). - Decide relationships: has-a by default, is-a only where substitution holds.
- Put the parts that vary behind interfaces, such as
PricingStrategyandSpotAssignmentStrategy. - Think about concurrency: two cars must not get the same spot, so guard the allocation.
- Write a small runnable driver to prove the flow.
The usual follow-up is to add a new vehicle type or pricing rule. If the design is right, that means adding a class, not editing old ones.
What are design patterns, and how do Singleton (thread-safe), Factory and Observer work?DeepSeveral reports
Design patterns are named, reusable solutions to recurring design problems. They give a shared vocabulary, but forcing one where it is not needed adds noise.
- Singleton: one instance and a global access point, using a private constructor and a static accessor. A naive lazy
if (instance == null)can create two instances under threads. Safe options are eager initialisation, double-checked locking with avolatilefield, a holder class, a function-local static in C++11, or a Java enum. - Factory: a method decides which concrete class to build and returns a common type, so callers do not name concrete classes.
- Observer: a subject keeps subscribers and notifies them on change, as in event listeners.
Singleton is often criticised for hiding dependencies and making tests hard, so dependency injection is the usual follow-up.
- Singleton: one instance and a global access point, using a private constructor and a static accessor. A naive lazy
Memory, lifetime and keywords
How does garbage collection work, and how is memory laid out in Java compared with C++?DeepSeveral reports
A garbage collector reclaims objects that are unreachable from the roots, such as stack variables and statics. The basic algorithm is mark and sweep: mark everything reachable, free the rest. Most JVM collectors are generational, because most objects die young: new objects go to a young generation collected often and cheaply by copying, and survivors are promoted to the old generation.
In Java each thread has a stack for frames, locals and references, and objects live on the shared heap. A memory leak there means an object stays reachable, for example in a static collection or a forgotten listener. Collections can pause the application.
C++ has no collector: objects live on the stack, the heap or in static storage, and memory is released by destructors under RAII:
unique_ptrowns one object, andshared_ptrreference-counts but leaks cycles, so break them withweak_ptr.A common follow-up asks what stops the world during a collection and how to tune it.
What is the difference between call by value and call by reference?CoreOne report
With call by value the function receives a copy of the argument, so changes to the parameter do not affect the caller. With call by reference the parameter is an alias of the caller's variable, so changes are visible, and no copy is made.
C++ supports both:
void inc(int x)leaves the caller alone, whilevoid inc(int& x)changes it. A pointer passes an address by value, which lets the callee modify the object it points to.Java is always pass by value. For objects, the value copied is the reference, so a method can mutate the object the caller sees, but assigning a new object to the parameter does not change the caller's variable.
The classic follow-up is to write a
swap. It works in C++ with references or pointers, and cannot work on plain Java variables.What do the static, final and volatile keywords mean?DeepSeveral reports
In Java:
- static: a member belongs to the class, with one shared copy, not to each instance. A static method has no
thisand cannot touch instance fields directly. - final: a variable can be assigned once, a method cannot be overridden and a class cannot be extended. A final reference cannot be rebound, but the object it points to can still change.
- volatile: a write by one thread is visible to others, and reads and writes are not reordered across it. It does not make compound actions atomic, so
count++on a volatile field can still lose updates; useAtomicIntegeror a lock.
C++ differs:
constis the closer match to final variables, andvolatiledoes not give thread safety, so usestd::atomic.A usual follow-up asks why double-checked locking needs volatile.
- static: a member belongs to the class, with one shared copy, not to each instance. A static method has no
How do String, StringBuilder and StringBuffer differ, and why is String immutable?CoreSeveral reports
In Java, String is immutable: every change builds a new object. That makes it safe to share across threads, safe as a hash map key because its hash cannot change, and cacheable in the string pool. The cost is that repeated concatenation in a loop copies the text each time, which is quadratic in total.
- StringBuilder is mutable, unsynchronised and fast. Use it for building text in one thread.
- StringBuffer is mutable with synchronised methods, so it is thread-safe but slower, and rarely needed now.
Example: building a result with
sb.append(part)in a loop is linear, whiles = s + partis not.To write your own immutable class, make it
final, keep all fieldsprivate final, provide no setters and copy any mutable input or output. The follow-up is usually where else immutability helps, such as concurrency.
Collections, exceptions and language features
How does a HashMap work internally? Cover hashing, collisions, equals and hashCode, and resizing.DeepSeveral reports
A hash map is an array of buckets. The key's
hashCodeis mixed and masked to pick a bucket index (Java uses a power-of-two capacity, so a bit mask replaces the modulo). Lookup finds the bucket, then compares keys withequals.- Collisions: keys sharing a bucket are chained in a list. Since Java 8, adding to a bucket that already holds 8 entries converts it to a red-black tree (if the table has fewer than 64 slots it resizes instead), so the worst case is O(log n).
- Contract: equal objects must return equal hash codes. Break it, or mutate a key after insertion, and lookups fail.
- Resizing: when size exceeds capacity times the load factor (0.75 by default), capacity doubles and entries are redistributed.
Average get and put are O(1). HashMap is not thread-safe; use
ConcurrentHashMap, which is the usual follow-up.What are the main Java 8 features, and how do streams and parallel streams work?DeepSeveral reports
Java 8 added lambdas, functional interfaces, method references, streams,
Optional, default methods on interfaces and a new date and time API.A stream is a pipeline over a source: intermediate steps such as
filterandmapare lazy, and nothing runs until a terminal step such ascollectorsum. A stream does not change its source and can be consumed once.Example:
names.stream().filter(n -> n.startsWith("A")).map(String::toUpperCase).collect(Collectors.toList()).A parallel stream splits the work across the common fork-join pool. It helps with large data and independent, stateless CPU-bound operations. It can hurt with small inputs, blocking I/O or shared mutable state, and ordering needs care.
The follow-up is usually when you would not use a parallel stream.
What do access specifiers control, and how does exception handling work?CorePrep guides
Access specifiers set who may use a member.
privatemeans only the class itself,protectedadds subclasses (and, in Java, the same package), andpublicmeans everyone. Java also has a default, package-private level. They enforce encapsulation by keeping the usable surface small.In Java, exceptions separate error handling from normal flow. Code that fails
throws; atryblock withcatchhandlers deals with it;finallyruns cleanup either way. Checked exceptions must be caught or declared, and unchecked ones (runtime errors) need not be.- Catch the most specific type first and do not swallow errors silently.
- Do not use exceptions for ordinary control flow.
- Prefer try-with-resources, or RAII in C++, to release resources.
A frequent follow-up is what happens if both
tryandfinallycontainreturn. The one infinallywins.
Keep going
- Machine coding round
- System design chapters
- Practice problems
- Operating systems questions
- Database management systems questions
- Computer networks questions
- SQL questions
Companies do not publish their questions and rotate them. These come from public candidate write-ups and prep guides, so treat the labels as one person's account or one list's tag. The answers are written for this site.