Company questionsGroundworkObject-oriented programming

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

  1. 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 BankAccount keeps balance private and changes it only through deposit and withdraw, 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 SavingsAccount and Account.
    • 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.

  2. 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 Stack interface with push and pop is 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 that push bounds-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.

  3. 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 default and static methods 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 Comparable or Runnable, or when you want callers to depend on a contract only.

    Example: an interface PaymentMethod with an abstract class BaseCard that 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

  1. 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) and area(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 the Circle version when the object is a Circle.

    Run-time polymorphism needs inheritance or an interface and, in C++, a virtual function. 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.

  2. 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 be virtual, and override catches mistakes.

    • Pitfall: with print(Object) and print(String), a call through an Object-typed variable holding a string still picks print(Object), because overloads use the static type.
    • Static methods are hidden, not overridden.
  3. 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.

  4. 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: D inherits from B and C, and both inherit from A. In C++ that gives D two copies of A, so d.x is ambiguous. Declare class B : virtual public A (and the same for C), and D holds one shared A, 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: C derived from B derived from A prints 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

  1. 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 Invoice from InvoicePrinter.
    • Open/closed: open to extension, closed to modification. Add a new DiscountRule class instead of editing an if chain.
    • Liskov substitution: a subtype must work wherever its base type is expected. A Square that breaks the Rectangle contract 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 OrderService takes a PaymentGateway interface through its constructor.

    Interviewers often ask you to spot which principle a given snippet breaks, or to refactor it.

  2. 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 Penguin that extends FlyingBird must disable flying. Instead give Bird a MoveBehaviour, and inject Walk or Fly. 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.

  3. 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.

    1. Clarify requirements and scope: vehicle types, pricing, payments, how many floors, what the caller can do.
    2. Take nouns as entities (ParkingLot, Floor, Spot, Vehicle, Ticket) and verbs as behaviours (park, unpark, pay).
    3. Decide relationships: has-a by default, is-a only where substitution holds.
    4. Put the parts that vary behind interfaces, such as PricingStrategy and SpotAssignmentStrategy.
    5. Think about concurrency: two cars must not get the same spot, so guard the allocation.
    6. 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.

  4. 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 a volatile field, 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.

Memory, lifetime and keywords

  1. 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_ptr owns one object, and shared_ptr reference-counts but leaks cycles, so break them with weak_ptr.

    A common follow-up asks what stops the world during a collection and how to tune it.

  2. 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, while void 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.

  3. 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 this and 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; use AtomicInteger or a lock.

    C++ differs: const is the closer match to final variables, and volatile does not give thread safety, so use std::atomic.

    A usual follow-up asks why double-checked locking needs volatile.

  4. 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, while s = s + part is not.

    To write your own immutable class, make it final, keep all fields private 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

  1. 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 hashCode is 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 with equals.

    • 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.

  2. 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 filter and map are lazy, and nothing runs until a terminal step such as collect or sum. 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.

  3. What do access specifiers control, and how does exception handling work?CorePrep guides

    Access specifiers set who may use a member. private means only the class itself, protected adds subclasses (and, in Java, the same package), and public means 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; a try block with catch handlers deals with it; finally runs 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 try and finally contain return. The one in finally wins.

Keep going

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.

Now make it stick