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.

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.

ParadigmUnit of organisationCore ideaLanguages
Imperative / proceduralProcedure, statementDescribe how: a sequence of statements that mutate stateC, Pascal, Fortran
Object-orientedObject (state + behaviour)Bundle data with the operations on it; let types vary behind one interfaceJava, C++, C#, Python
FunctionalFunction (first-class value)Describe what: pure functions, immutable data, no side effectsHaskell, Erlang, Lisp, Scala
Declarative / logicRule, constraint, queryState the goal; a solver or engine works out the stepsSQL, 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)ScopeLifetimeStored in
auto (default local)Block it is declared inUntil the block exitsStack
static localBlock it is declared inWhole program runData segment
static globalThat source file onlyWhole program runData segment
externEvery file that declares itWhole program runData segment
registerBlock it is declared inUntil the block exitsA 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.

CThe classic scoping question
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.

SegmentHoldsGrowsManaged by
Text (code)Machine instructions; usually read-only and shareableFixedLoader
DataInitialised globals and staticsFixedLoader
BSSUninitialised globals and statics (zero-filled at load)FixedLoader
HeapDynamic allocations (malloc, new)UpwardProgrammer or GC
StackCall frames: locals, parameters, return addressesDownwardCompiler, 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.

StackHeap
Allocation costOne pointer adjustment — effectively freeSearch a free list; may need a syscall
DeallocationAutomatic on function returnExplicit free/delete, or a garbage collector
SizeSmall and fixed (typically 1–8 MB per thread)Large; limited by available memory
LifetimeBounded by the enclosing callArbitrary — outlives the creating function
FragmentationImpossible (strict LIFO)Yes — the main long-run problem
Thread safetyEach thread has its ownShared; allocation must be synchronised
Typical failureStack overflow (deep or infinite recursion)Memory leak, dangling pointer, double free
CAll three failures in eight lines
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..9

Parameter passing

Reliably asked, and reliably answered badly. There are three mechanisms worth knowing and one very common misconception.

MechanismWhat is copiedCaller's variable changes?Found in
Pass by valueThe value itselfNoC, C++ (default), Java (always)
Pass by referenceNothing — the parameter is the caller's variable, aliasedYes, including reassignmentC++ (int&), C# (ref), Pascal (var)
Pass by pointerThe address, by valueThe pointed-to object yes; the caller's pointer noC, C++, Go
JavaWhy the distinction is not pedantry
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 untouched

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

  1. Encapsulation

    Bundle state with the methods that operate on it, and restrict direct access to that state. Problem solved: invariants. If balance is private and only withdraw() can change it, the rule "balance never goes negative" can be enforced in one place instead of trusted everywhere.
  2. Abstraction

    Expose only what a caller needs, hiding the mechanism. Problem solved: coupling. A caller of list.sort() does not depend on which algorithm runs, so the implementation can be replaced without touching callers.
  3. Inheritance

    Derive a new type from an existing one, acquiring its members. Problem solved: reuse plus subtyping — a Dog is-a Animal, so anything written against Animal works for Dog unchanged.
  4. 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

RelationshipReads asMechanismCoupling
InheritanceDog is-a Animalclass Dog extends AnimalTight, compile-time, permanent
CompositionCar has-a EngineA field holding another objectLoose, swappable at run time
AggregationDepartment has Teachers (who outlive it)A field, but shared ownershipLoose; parts survive the whole
AssociationStudent uses LibraryA reference or parameterWeakest

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

LanguageResolution
C++Allows it. Two copies of A by default; virtual inheritance collapses them into one shared base
JavaForbids 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
PythonAllows it, resolved by the C3 linearisation MRO — a deterministic order you can inspect via ClassName.__mro__
C#, RubySingle class inheritance plus interfaces or mixins
PythonPython's MRO makes the diamond explicit
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 once

Polymorphism and dynamic dispatch

Compile-time (static)Run-time (dynamic)
Also calledOverloading, early bindingOverriding, late binding
Resolved byThe compiler, from argument typesThe runtime, from the object's actual type
MechanismName mangling / signature matchingVirtual table (vtable) lookup
SignatureMust differ (arity or parameter types)Must be identical
CostNone — it is a compile-time choiceOne indirect call through the vtable
Exampleadd(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.

C++The bug this explains
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 classInterface
InstantiableNoNo
Method bodiesYes, may mix abstract and concreteHistorically none; now default methods are allowed
Instance stateYes — fields, constructorsNo instance fields; constants only
Multiple inheritanceOne per classMany per class
ConstructorsYes (run via super())No
Access modifiersAnyImplicitly public
ModelsAn is-a relationship with shared implementationA 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.

  1. S — Single Responsibility

    A class should have one reason to change. Violation: a Report class that computes totals, formats HTML and emails the result — three teams can force three unrelated edits to the same file.
  2. O — Open/Closed

    Open for extension, closed for modification. Violation: a switch on shape type inside area() that must be edited every time a shape is added. Fix: let each shape implement area().
  3. L — Liskov Substitution

    A subtype must be usable anywhere its supertype is, without the caller noticing. Violation: Square extends Rectangle — setting width on a Square silently changes its height, breaking code that assumed the two were independent.
  4. I — Interface Segregation

    No client should be forced to depend on methods it does not use. Violation: one fat Machine interface with print, scan and fax, forcing a simple printer to implement fax() by throwing.
  5. D — Dependency Inversion

    Depend on abstractions, not concretions; high-level policy must not import low-level detail. Violation: an order service that constructs new 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.

FamilyConcerned withMembers you should know
CreationalHow objects are madeSingleton, Factory Method, Abstract Factory, Builder, Prototype
StructuralHow objects are composedAdapter, Decorator, Facade, Proxy, Composite, Bridge
BehaviouralHow objects communicateObserver, Strategy, Command, Iterator, State, Template Method
PatternProblem it solvesA real example
SingletonExactly one instance, globally reachableA connection pool, a logger, an app config
Factory MethodCreate objects without naming the concrete class at the call siteCalendar.getInstance() returning a locale-specific calendar
BuilderConstruct an object with many optional parts, step by stepStringBuilder; any fluent request builder
AdapterMake an existing class fit an interface it does not implementWrapping a legacy XML API behind a JSON interface
DecoratorAdd behaviour to one object without subclassingBufferedReader(new FileReader(f))
FacadeOne simple entry point over a complicated subsystemA Compiler class hiding lexer, parser and code generator
ProxyStand in for another object to control accessLazy loading, access checks, an RPC client stub
ObserverNotify many dependents when one object changesUI event listeners; a pub/sub topic
StrategySwap an algorithm at run timePassing 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.

KindMeaningJava exampleShould you catch it?
CheckedRecoverable and anticipated; the compiler forces you to declare or handle itIOException, SQLExceptionYes — you have a plan for it
Unchecked (runtime)A programming bug; not required to be declaredNullPointerException, IllegalArgumentExceptionUsually no — fix the bug instead
ErrorThe JVM or environment is failingOutOfMemoryError, StackOverflowErrorNo — you cannot meaningfully recover
  • finally runs whether or not an exception was thrown — it is where you release resources. It runs even after a return in the try block.
  • A return inside finally discards 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 noexcept by default.
  • Prefer try-with-resources (Java) or RAII (C++) over manual finally blocks: the compiler cannot forget to clean up.

Self-check

10 questions on Programming Fundamentals & OOP · nothing is stored

0/10 answered
Question 1

1.In Java, what happens if a method reassigns its object parameter to a new object?

Question 2

2.A static local variable in C differs from an ordinary local variable in that it has:

Question 3

3.Which pair of declarations is NOT a valid overload?

Question 4

4.What is the difference between abstraction and encapsulation?

Question 5

5.Deleting a derived object through a base-class pointer whose destructor is not virtual:

Question 6

6.Square extends Rectangle, where setting the width of a Square also changes its height, violates which principle?

Question 7

7.Does a garbage-collected language make memory leaks impossible?

Question 8

8.Java forbids multiple class inheritance primarily to avoid:

Question 9

9.Adapter, Decorator and Proxy all wrap another object. What distinguishes them?

Question 10

10.What does a return statement inside a finally block do to an exception thrown in the try block?