Programming Fundamentals & OOP
How programs are structured before any of the rest matters: memory, scope, parameter passing, the four pillars, and the design principles examiners actually ask you to name.
If you remember nothing else
- The four pillars are encapsulation, abstraction, inheritance and polymorphism — and abstraction is about the interface you expose, encapsulation about the state you hide.
- Stack memory is automatic and scoped; heap memory is manual (or garbage-collected) and outlives the function that created it.
- Java is always pass-by-value — object references are themselves passed by value, which is why reassigning a parameter never affects the caller.
- Compile-time polymorphism is overloading, resolved by signature; run-time polymorphism is overriding, resolved through a vtable.
- Prefer composition over inheritance: inheritance couples you to a parent's implementation forever, composition to an interface you choose.
- SOLID in one line each: one reason to change; extend don't modify; subtypes must substitute; small interfaces; depend on abstractions.
11 chapters
- 1Programming paradigms
- 2Scope, lifetime and storage
- 3Stack, heap and the memory layout of a process
- 4Parameter passing
- 5The four pillars of OOP
- 6Inheritance, composition and the diamond problem
- 7Polymorphism and dynamic dispatch
- 8Abstract classes vs interfaces
- 9SOLID principles
- 10Design patterns worth naming
- 11Exceptions and error handling
Programming paradigms
A paradigm is a style of organising computation — what the primary unit of a program is, and what you are allowed to do with it. Most languages you will name in an interview are multi-paradigm; the question is which style they make easiest.
| Paradigm | Unit of organisation | Core idea | Languages |
|---|---|---|---|
| Imperative / procedural | Procedure, statement | Describe how: a sequence of statements that mutate state | C, Pascal, Fortran |
| Object-oriented | Object (state + behaviour) | Bundle data with the operations on it; let types vary behind one interface | Java, C++, C#, Python |
| Functional | Function (first-class value) | Describe what: pure functions, immutable data, no side effects | Haskell, Erlang, Lisp, Scala |
| Declarative / logic | Rule, constraint, query | State the goal; a solver or engine works out the steps | SQL, Prolog, HTML |
Scope, lifetime and storage
Two properties are constantly confused, and separating them cleanly answers a whole family of questions. Scope is where in the source text a name is visible. Lifetime is how long in time the storage behind it exists. They are independent: a static local variable has narrow scope and program-long lifetime.
| Storage class (C) | Scope | Lifetime | Stored in |
|---|---|---|---|
auto (default local) | Block it is declared in | Until the block exits | Stack |
static local | Block it is declared in | Whole program run | Data segment |
static global | That source file only | Whole program run | Data segment |
extern | Every file that declares it | Whole program run | Data segment |
register | Block it is declared in | Until the block exits | A CPU register, if the compiler agrees |
Static vs dynamic scoping
Under static (lexical) scoping, a free variable resolves to the enclosing block in the source text — you can determine the binding by reading the program. Under dynamic scoping, it resolves to the most recent binding on the call stack, so the answer depends on who called you. Essentially every modern language is lexically scoped, because dynamic scoping makes a function's meaning unpredictable.
int x = 10;
void show(void) { printf("%d\n", x); }
void caller(void) { int x = 20; show(); }
// Lexical scoping (what C actually does): prints 10.
// show() sees the global x - the x in caller() is a different variable
// that show() cannot name.
// Dynamic scoping (hypothetical): would print 20.
// show() would find the most recent binding of x on the call stack.Stack, heap and the memory layout of a process
A running process gets one address space, conventionally divided into five regions. Knowing the order and what lives where answers most memory questions directly.
| Segment | Holds | Grows | Managed by |
|---|---|---|---|
| Text (code) | Machine instructions; usually read-only and shareable | Fixed | Loader |
| Data | Initialised globals and statics | Fixed | Loader |
| BSS | Uninitialised globals and statics (zero-filled at load) | Fixed | Loader |
| Heap | Dynamic allocations (malloc, new) | Upward | Programmer or GC |
| Stack | Call frames: locals, parameters, return addresses | Downward | Compiler, automatically |
Heap and stack grow toward each other. Historically, their collision was the failure; today the OS keeps them far apart in a large virtual address space.
| Stack | Heap | |
|---|---|---|
| Allocation cost | One pointer adjustment — effectively free | Search a free list; may need a syscall |
| Deallocation | Automatic on function return | Explicit free/delete, or a garbage collector |
| Size | Small and fixed (typically 1–8 MB per thread) | Large; limited by available memory |
| Lifetime | Bounded by the enclosing call | Arbitrary — outlives the creating function |
| Fragmentation | Impossible (strict LIFO) | Yes — the main long-run problem |
| Thread safety | Each thread has its own | Shared; allocation must be synchronised |
| Typical failure | Stack overflow (deep or infinite recursion) | Memory leak, dangling pointer, double free |
int *p = malloc(sizeof(int) * 10);
p = malloc(sizeof(int) * 20); // LEAK: first block is now unreachable
int *q = malloc(sizeof(int));
free(q);
*q = 5; // DANGLING: q still holds the old address
free(q); // DOUBLE FREE: allocator metadata corrupted
int arr[10];
arr[10] = 1; // OFF BY ONE: valid indices are 0..9Parameter passing
Reliably asked, and reliably answered badly. There are three mechanisms worth knowing and one very common misconception.
| Mechanism | What is copied | Caller's variable changes? | Found in |
|---|---|---|---|
| Pass by value | The value itself | No | C, C++ (default), Java (always) |
| Pass by reference | Nothing — the parameter is the caller's variable, aliased | Yes, including reassignment | C++ (int&), C# (ref), Pascal (var) |
| Pass by pointer | The address, by value | The pointed-to object yes; the caller's pointer no | C, C++, Go |
static void mutate(StringBuilder sb) { sb.append(" world"); }
static void reassign(StringBuilder sb) { sb = new StringBuilder("other"); }
StringBuilder s = new StringBuilder("hello");
mutate(s); // s is now "hello world" - the object was modified
reassign(s); // s is STILL "hello world" - only the local copy of the
// reference was repointed; the caller's reference is untouchedTwo mechanisms you only need to recognise
- Pass by value-result (copy-in/copy-out) — the argument is copied in on entry and copied back out on return. Differs from pass by reference when the same variable is passed twice or is modified concurrently. Used by Ada
in out. - Pass by name — the argument expression is substituted textually and re-evaluated at every use, like a macro. Notorious for surprising results; Algol 60's contribution to exam questions.
The four pillars of OOP
Name them, define them in one sentence each, and — this is what separates a good answer — say what problem each one solves.
Encapsulation
Bundle state with the methods that operate on it, and restrict direct access to that state. Problem solved: invariants. Ifbalanceis private and onlywithdraw()can change it, the rule "balance never goes negative" can be enforced in one place instead of trusted everywhere.Abstraction
Expose only what a caller needs, hiding the mechanism. Problem solved: coupling. A caller oflist.sort()does not depend on which algorithm runs, so the implementation can be replaced without touching callers.Inheritance
Derive a new type from an existing one, acquiring its members. Problem solved: reuse plus subtyping — aDogis-aAnimal, so anything written againstAnimalworks forDogunchanged.Polymorphism
One interface, many implementations, selected by the actual type. Problem solved: extensibility. Adding a new shape requires no change to the code that draws shapes.
Inheritance, composition and the diamond problem
| Relationship | Reads as | Mechanism | Coupling |
|---|---|---|---|
| Inheritance | Dog is-a Animal | class Dog extends Animal | Tight, compile-time, permanent |
| Composition | Car has-a Engine | A field holding another object | Loose, swappable at run time |
| Aggregation | Department has Teachers (who outlive it) | A field, but shared ownership | Loose; parts survive the whole |
| Association | Student uses Library | A reference or parameter | Weakest |
The diamond problem
If B and C both inherit from A, and D inherits from both B and C, then D has two paths back to A. If A has a field or method, does D get one copy or two, and which override wins? That ambiguity is the diamond (or deadly diamond of death).
| Language | Resolution |
|---|---|
| C++ | Allows it. Two copies of A by default; virtual inheritance collapses them into one shared base |
| Java | Forbids multiple class inheritance outright. Multiple interfaces are allowed; since Java 8 default methods can clash, and the compiler forces you to override and disambiguate explicitly |
| Python | Allows it, resolved by the C3 linearisation MRO — a deterministic order you can inspect via ClassName.__mro__ |
| C#, Ruby | Single class inheritance plus interfaces or mixins |
class A:
def who(self): return "A"
class B(A):
def who(self): return "B"
class C(A):
def who(self): return "C"
class D(B, C):
pass
print(D().who()) # "B" - B precedes C in the MRO
print([c.__name__ for c in D.__mro__])
# ['D', 'B', 'C', 'A', 'object'] - each class appears exactly oncePolymorphism and dynamic dispatch
| Compile-time (static) | Run-time (dynamic) | |
|---|---|---|
| Also called | Overloading, early binding | Overriding, late binding |
| Resolved by | The compiler, from argument types | The runtime, from the object's actual type |
| Mechanism | Name mangling / signature matching | Virtual table (vtable) lookup |
| Signature | Must differ (arity or parameter types) | Must be identical |
| Cost | None — it is a compile-time choice | One indirect call through the vtable |
| Example | add(int,int) and add(double,double) | Animal a = new Dog(); a.speak(); |
How dynamic dispatch actually works
Each class with virtual methods gets one vtable: an array of function pointers, one slot per virtual method. Every object of that class carries a hidden vptr to its class's vtable. A virtual call compiles to "load the vptr, index the fixed slot, call through it" — so the cost is one extra indirection, and the resolution genuinely happens at run time.
struct Base {
void f() { cout << "Base::f"; } // NOT virtual
virtual void g() { cout << "Base::g"; } // virtual
virtual ~Base() = default; // essential - see below
};
struct Derived : Base {
void f() { cout << "Derived::f"; }
void g() override { cout << "Derived::g"; }
};
Base* p = new Derived();
p->f(); // "Base::f" - resolved statically from the pointer's type
p->g(); // "Derived::g" - resolved dynamically through the vtable
delete p; // Calls ~Derived then ~Base ONLY because ~Base is virtual.
// Without that, this is undefined behaviour and leaks Derived's members.Abstract classes vs interfaces
| Abstract class | Interface | |
|---|---|---|
| Instantiable | No | No |
| Method bodies | Yes, may mix abstract and concrete | Historically none; now default methods are allowed |
| Instance state | Yes — fields, constructors | No instance fields; constants only |
| Multiple inheritance | One per class | Many per class |
| Constructors | Yes (run via super()) | No |
| Access modifiers | Any | Implicitly public |
| Models | An is-a relationship with shared implementation | A can-do capability contract |
SOLID principles
Five design principles that examiners ask you to expand and illustrate. Give the name, the one-line rule, and a concrete violation.
S — Single Responsibility
A class should have one reason to change. Violation: aReportclass that computes totals, formats HTML and emails the result — three teams can force three unrelated edits to the same file.O — Open/Closed
Open for extension, closed for modification. Violation: aswitchon shape type insidearea()that must be edited every time a shape is added. Fix: let each shape implementarea().L — Liskov Substitution
A subtype must be usable anywhere its supertype is, without the caller noticing. Violation:Square extends Rectangle— setting width on aSquaresilently changes its height, breaking code that assumed the two were independent.I — Interface Segregation
No client should be forced to depend on methods it does not use. Violation: one fatMachineinterface withprint,scanandfax, forcing a simple printer to implementfax()by throwing.D — Dependency Inversion
Depend on abstractions, not concretions; high-level policy must not import low-level detail. Violation: an order service that constructsnew MySqlConnection()internally, making it impossible to test or re-target.
Design patterns worth naming
The Gang of Four catalogued 23 patterns in three families. You are rarely asked to recite all of them; you are frequently asked to explain a handful and give a real use.
| Family | Concerned with | Members you should know |
|---|---|---|
| Creational | How objects are made | Singleton, Factory Method, Abstract Factory, Builder, Prototype |
| Structural | How objects are composed | Adapter, Decorator, Facade, Proxy, Composite, Bridge |
| Behavioural | How objects communicate | Observer, Strategy, Command, Iterator, State, Template Method |
| Pattern | Problem it solves | A real example |
|---|---|---|
| Singleton | Exactly one instance, globally reachable | A connection pool, a logger, an app config |
| Factory Method | Create objects without naming the concrete class at the call site | Calendar.getInstance() returning a locale-specific calendar |
| Builder | Construct an object with many optional parts, step by step | StringBuilder; any fluent request builder |
| Adapter | Make an existing class fit an interface it does not implement | Wrapping a legacy XML API behind a JSON interface |
| Decorator | Add behaviour to one object without subclassing | BufferedReader(new FileReader(f)) |
| Facade | One simple entry point over a complicated subsystem | A Compiler class hiding lexer, parser and code generator |
| Proxy | Stand in for another object to control access | Lazy loading, access checks, an RPC client stub |
| Observer | Notify many dependents when one object changes | UI event listeners; a pub/sub topic |
| Strategy | Swap an algorithm at run time | Passing a Comparator to a sort |
Exceptions and error handling
An exception separates the detection of an error from its handling, so intermediate functions need no error-checking code at all. The cost is a non-obvious control-flow path.
| Kind | Meaning | Java example | Should you catch it? |
|---|---|---|---|
| Checked | Recoverable and anticipated; the compiler forces you to declare or handle it | IOException, SQLException | Yes — you have a plan for it |
| Unchecked (runtime) | A programming bug; not required to be declared | NullPointerException, IllegalArgumentException | Usually no — fix the bug instead |
| Error | The JVM or environment is failing | OutOfMemoryError, StackOverflowError | No — you cannot meaningfully recover |
finallyruns whether or not an exception was thrown — it is where you release resources. It runs even after areturnin thetryblock.- A
returninsidefinallydiscards any in-flight exception, silently swallowing the failure. Never do it. - Catch the most specific exception first; a
catch (Exception e)before a specific one is unreachable, and most compilers reject it. - Throwing from a destructor (C++) during stack unwinding terminates the program — which is why C++11 made destructors
noexceptby default. - Prefer try-with-resources (Java) or RAII (C++) over manual
finallyblocks: the compiler cannot forget to clean up.
Self-check
10 questions on Programming Fundamentals & OOP · nothing is stored