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.
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 |
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 |
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.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..9Reliably 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 untouchedin out.Name them, define them in one sentence each, and - this is what separates a good answer - say what problem each one solves.
Encapsulation
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.Abstraction
list.sort() does not depend on which algorithm runs, so the implementation can be replaced without touching callers.Inheritance
Dog is-a Animal, so anything written against Animal works for Dog unchanged.Polymorphism
| 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 |
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 once| 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(); |
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 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 |
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
Report class that computes totals, formats HTML and emails the result - three teams can force three unrelated edits to the same file.O - Open/Closed
switch on shape type inside area() that must be edited every time a shape is added. Fix: let each shape implement area().L - Liskov Substitution
Square extends Rectangle - setting width on a Square silently changes its height, breaking code that assumed the two were independent.I - Interface Segregation
Machine interface with print, scan and fax, forcing a simple printer to implement fax() by throwing.D - Dependency Inversion
new MySqlConnection() internally, making it impossible to test or re-target.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 |
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 |
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.return inside finally discards any in-flight exception, silently swallowing the failure. Never do it.catch (Exception e) before a specific one is unreachable, and most compilers reject it.noexcept by default.finally blocks: the compiler cannot forget to clean up.10 questions on Programming Fundamentals & OOP · misses join your review queue, on this device only