Memory · OOP · STL · JVM · Streams
A technical primer for backend fundamentals
Stack, heap, pointers & smart pointers
Encapsulation, inheritance, polymorphism
Containers, algorithms, generic code
Lambdas, move semantics, exceptions
JVM, types, OOP, collections, streams
Tradeoffs, performance, real-world use
Memory · OOP · STL · Modern Features
int x = 5; // stack — gone when } runsnew, free with deleteint* p = new int(5); delete p;A variable that stores a memory address. Use & to get an address, * to read or write through it. The pointer IS NOT the value — it is a map to where the value lives.
Adding 1 advances by sizeof(T) bytes. arr + 3 points to the 4th element. Useful for traversing arrays — dangerous if you go out of bounds.
Dangling pointer — points to freed memory. Double-free — calling delete twice crashes your program. Null dereference — accessing nullptr causes a segfault. Always set to nullptr after deleting.
OS kernels, embedded systems, C API interop, performance-critical inner loops. Everywhere else — prefer smart pointers.
One and only one unique_ptr owns the resource. It cannot be copied — only moved. This enforces clear, unambiguous ownership in your architecture.
When the unique_ptr goes out of scope its destructor calls delete automatically. Memory cannot leak. Zero overhead compared to a raw pointer.
Exception-safe, no redundant type names, prevents half-constructed objects. Available C++14 onwards. Prefer it over new 100% of the time in modern code.
Ownership can be transferred explicitly: auto b = std::move(a). After the move, a becomes nullptr. The transfer of responsibility is visible in the code.
Multiple pointers share one resource. An internal counter tracks owners. Memory is freed only when the count reaches zero.
If A owns B and B owns A through shared_ptrs, the count never hits zero. Memory leaks permanently. This is the most common shared_ptr bug.
Does not increment the count. Must call lock() to get a temporary shared_ptr. Safely detects whether the object has already been freed.
Use shared_ptr for true shared ownership. Use weak_ptr for back-references, caches, and observer patterns — anywhere you observe without owning.
Bundle data and methods together. Hide internals with private. Only expose a clean public surface. Like a TV remote — press buttons, do not see the circuit board.
A derived class IS-A base class. Reuse and extend behaviour. Lab is-a Dog is-an Animal. Use : public Base to inherit.
Same interface, different behaviour. Runtime dispatch via vtable. virtual plus override. Say speak — Dog barks, Cat meows.
Expose WHAT to do, hide HOW it is done. Pure virtual equals zero creates abstract interfaces that derived classes must fill in.
private — this class only. protected — this class and derived classes. public — everyone. Default in class is private. Default in struct is public.
Constructs members directly rather than default-constructing and then assigning. Faster and required for const members and references.
A const method promises it will not modify this object. Enables calling on const references and self-documents intent.
If you use smart pointers and STL types for all resources you need zero special copy, move, or destructor logic. The compiler generates correct behaviour for free.
Derived class IS-A base class. Use only for true IS-A relationships, not just to reuse code. Lab is-a Dog — that works. Reusing code between unrelated classes — use composition instead.
Base constructor runs first. Chain it via initializer list: Lab(string n) : Dog(n, 1) {}. Destruction runs in reverse — derived destroyed first, then base.
C++ allows multiple parents. The Diamond Problem: two paths lead to the same grandparent causing duplicate copies. Solved with virtual base classes.
Any class with virtual methods must have a virtual destructor. Otherwise deleting through a base pointer causes undefined behaviour — the derived destructor never runs.
| Container | Access | Insert / Erase | Sorted? | Best For |
|---|---|---|---|---|
| vector<T> | O(1) | O(1) amort. at end | — | Default — cache-friendly sequence |
| deque<T> | O(1) | O(1) both ends | — | Fast front and back access |
| list<T> | O(n) | O(1) anywhere | — | Frequent mid-sequence edits |
| map<K,V> | O(log n) | O(log n) | Yes | Sorted key-value, range queries |
| unordered_map<K,V> | O(1) avg | O(1) avg | — | Fastest key-value lookups |
| set<T> | O(log n) | O(log n) | Yes | Unique sorted elements |
| priority_queue<T> | O(1) top | O(log n) | Heap | Always get the largest element first |
Write once, works for any type. The compiler generates type-specific versions at compile time. Zero runtime overhead — full optimisation per type.
Parameterise an entire class. Every STL container is a class template — vector<T>, map<K,V>. Type safety is enforced at compile time.
Provide a custom implementation for a specific type when the generic version is not optimal. For example a Stack<bool> that uses bitwise storage.
C++ generates unique code per type — faster but larger binary. Java uses type erasure — one bytecode, type checked at compile time only. Different tradeoffs entirely.
[capture](params) { body }
Capture brings in outer variables. Params are arguments. Body is the work.
[=] capture all by value (copy)[&] capture all by reference[x, &y] mixed
auto stores a lambda with zero overhead and the compiler can inline it. std::function adds type erasure and runtime cost — use it only when you need polymorphic storage.
Replaces separate functor classes entirely. Pass inline to sort, find_if, transform. Cleaner than writing a named struct just for one comparator.
lvalue: named, persists — like int x = 5. rvalue: temporary, about to die — like 42 or foo(). T&& binds to rvalues only.
Grabs the internal buffer and nulls out the source. Order one regardless of data size. Critical for returning large objects from functions efficiently.
Tells the compiler you give up ownership. Enables the move constructor instead of the copy constructor. The source is empty after — do not read it.
Mark move constructors noexcept or STL containers like vector will fall back to copying during reallocation. The noexcept guarantee is checked at compile time.
Throw creates a copy. Catching by const reference avoids another copy and prevents object slicing. Catch most-specific exceptions first, the base class last.
std::exception is the root. Children include runtime_error, logic_error, bad_alloc, out_of_range. Catch the base to handle all of them in one block.
Destructors run even during exception unwinding. Smart pointers and lock_guard auto-clean up. This is more reliable than finally because you cannot forget to write it.
Promise the function will not throw. Enables compiler optimisations. Required for move constructors to be used by STL containers during reallocation.
JVM · OOP · Collections · Streams
Bytecode is OS-agnostic. Any machine with a JVM runs the same dot class file — Windows, Linux, macOS, Android, smart cards.
Automatically reclaims heap memory. Modern GCs: G1GC is default, ZGC and Shenandoah for ultra-low pause. No manual delete ever needed.
Profiles runtime behaviour and compiles hot methods to native code. Long-running Java servers often match C++ speed after the JVM warms up.
byte, short, int, long, float, double, boolean, char. Stored by value. Fast. Cannot be null.
String, arrays, all objects. Variable holds a reference. All classes inherit from Object. Can be null — which is the source of the NPE.
Java auto-converts primitive to wrapper and back. int to Integer is implicit. Convenient — but pay attention in performance-sensitive loops.
Local type inference. Compiler infers the type from the initializer expression. Only valid for local variables — not fields or parameters.
No free functions, no global variables. Everything lives inside a class. The entry point is public static void main taking a String array.
toString, equals, and hashCode are inherited by every object. Always override equals and hashCode together — HashMap depends on this contract being correct.
GC handles memory. For files and connections: use try-with-resources with AutoCloseable. The close method is called automatically even when an exception fires.
public, protected, package-private (the default, no keyword), private. The default is package-private — stricter than C++.
Defines WHAT, not HOW. All methods are implicitly public and abstract. A class can implement any number of interfaces — Java's answer to multiple inheritance.
Interfaces can have concrete methods via the default keyword. Allows adding new methods to existing interfaces without breaking every class that implements them.
Can have fields, constructors, and a mix of concrete and abstract methods. Use for shared state across subclasses. You can extend only one abstract class.
Interface for capability contracts: Flyable, Comparable, Runnable. Abstract class for partial implementation with shared data. When in doubt: interface.
| Interface | Implementation | Ordered? | Duplicates? | C++ Equivalent |
|---|---|---|---|---|
| List | ArrayList | Insertion order | Yes | vector<T> |
| List | LinkedList | Insertion order | Yes | list<T> |
| Map | HashMap | No | Keys: No | unordered_map<K,V> |
| Map | TreeMap | Sorted by key | Keys: No | map<K,V> |
| Set | HashSet | No | No | unordered_set<T> |
| Set | TreeSet | Sorted | No | set<T> |
| Queue | PriorityQueue | Heap order | Yes | priority_queue<T> |
Before generics ArrayList held Object — runtime ClassCastException risk. Generics catch type mismatches at compile time. Much safer development.
Generic info is erased at runtime. List<String> becomes List in bytecode. You cannot write new T() or use instanceof T at runtime because the JVM does not know what T is.
T extends Comparable<T> restricts T to types that implement Comparable. Enables calling compareTo inside the generic code safely.
Producer Extends, Consumer Super. Question mark extends T means read from it. Question mark super T means write to it.
Intermediate operations like filter and map do not execute until a terminal is called. This enables short-circuiting — findFirst stops scanning the moment it finds a match.
String double colon toUpperCase is shorthand for s arrow s.toUpperCase. Four forms exist: static method, instance method, constructor, and unbound instance.
toList, groupingBy, joining, counting — powerful aggregation operations. Use toUnmodifiableList for defensive defensive-safe return values from methods.
Checked — extends Exception — must be caught or declared. Unchecked — extends RuntimeException — optional. Error is a JVM-level problem like OutOfMemoryError — unrecoverable.
Any AutoCloseable declared in the try parentheses has close called automatically. No finally block needed. Works even if the body throws an exception — no resource leaks.
catch (IOException | SQLException e) handles multiple types in one block. DRY and readable. The caught exception is effectively final inside the block.
Extend Exception for checked, RuntimeException for unchecked. Pass context through the super constructor. Modern practice: prefer unchecked for programming errors.
The billion-dollar mistake. Calling any method on null crashes at runtime with no compile-time warning. Optional makes the absence of a value explicit and forces you to handle it.
Optional.of throws if you pass null. Optional.ofNullable wraps whatever you give it — null becomes empty. Optional.empty creates an explicitly empty container.
get throws NoSuchElementException when empty — no better than NPE. Use orElse, orElseGet, orElseThrow, or chain with map and filter instead.
Return Optional from methods that might not produce a value. Do not use Optional as a field type, in collections, or as a method parameter — those are anti-patterns.
Concise data-carrier classes. One line replaces constructor, getters, equals, hashCode, and toString. Fields are implicitly private and final — naturally immutable.
Restrict which classes can extend or implement a type. The compiler knows all subtypes exhaustively — enables total pattern matching without a default case.
Java 16+. Instead of casting after instanceof, the variable is bound directly: if (obj instanceof String s) then s is already a String inside the block.
Multi-line strings with triple-quote syntax. No more escaped newlines and concatenation for JSON, SQL, or HTML literals embedded in Java source.
Only one instance ever. Private constructor plus static instance getter. Used for configs, logging, connection pools.
Config.getInstance()Create objects without specifying exact class. Decouples creation from usage. The caller does not know which subclass it receives.
ShapeFactory.create("circle")Objects subscribe to be notified of changes. Event-driven systems, GUI callbacks, reactive streams. Also called Publisher-Subscriber.
bus.subscribe(listener)Swap algorithms at runtime. Define a family of behaviours, encapsulate each, make them interchangeable. Open-closed principle in action.
sorter.setStrategy(new QuickSort())find, find_if, count_if, binary_search, lower_bound, upper_bound, equal_range
sort, stable_sort, partial_sort, nth_element — all accept lambda comparators
transform, for_each, copy, copy_if, fill, replace, remove_if, rotate, reverse
accumulate, reduce, inner_product, min_element, max_element, minmax_element
C++ gives direct memory control — smart pointers make it safe through RAII, not manual delete calls
OOP's four pillars apply to both languages — same concepts, different syntax and enforcement rules
STL containers, algorithms, and templates give C++ expressive generic programming with zero overhead
Modern C++ — lambdas and move semantics — eliminates most reasons to write manual memory management
Java's JVM means write once run anywhere — the GC and JIT handle memory and performance automatically
Streams, generics, and interfaces give Java clean expressive architecture for large backend systems
NEXT STEPS
An alias for an existing variable. Cannot be null. Cannot be reseated once bound. Safer and cleaner than raw pointers for everyday use.
Avoids copying and lets the function modify the caller's data. Use when you need to mutate an argument or avoid an expensive copy of a large object.
Pass large objects as const T& — no copy, no modification. The cheapest and safest way to pass read-only data into a function.
for (const auto& item : vec) iterates without copying each element. Without the & you get a full copy on every iteration — expensive for strings and large objects.
Define what operators like +, ==, << mean for custom types. Makes mathematical and container types read naturally.
Symmetric operators like + and == are often free functions. Unary operators and assignment operators are typically members.
Lets you write cout << myObj. Must be a free function taking ostream& and returning ostream& to support chaining.
Java deliberately excludes operator overloading. The only exception is + for String concatenation — baked into the compiler, not user-definable.
Exactly one abstract method. The @FunctionalInterface annotation enforces this at compile time. Enables lambdas and method references anywhere the interface is expected.
Takes a T, returns boolean. Used in stream.filter(). Example: Predicate<String> isLong = s -> s.length() > 5
Takes a T, returns an R. Used in stream.map(). Example: Function<String, Integer> len = String::length
Consumer takes T, returns nothing — used in forEach. Supplier takes nothing, returns T — used for lazy evaluation and factory patterns.
Two threads read-modify-write the same variable simultaneously. The result is unpredictable. Classic case: two threads both incrementing a counter, losing one increment.
Only one thread enters the block at a time. Solves race conditions but can cause bottlenecks. Coarse-grained — use only when necessary.
Uses CPU compare-and-swap hardware instructions. Thread-safe, faster than synchronized for single-variable operations. Part of java.util.concurrent.atomic.
Chain async operations without blocking. thenApply, thenCombine, exceptionally. The modern way to write non-blocking backend code.
Compiler infers the type from the initializer. Not dynamic typing — the type is compile-time fixed, just not written redundantly. Reduces verbosity significantly.
for (const auto& x : container) replaces begin/end iterator loops. Cleaner, less error-prone, works with any type that provides begin and end.
Unpack pairs and tuples directly: auto [key, val] = entry. Much cleaner than accessing .first and .second repeatedly.
A value that may or may not be present. No null pointers, no sentinel values. Check with has_value(), get with value_or(default).
A class should have one reason to change. User class handles user data — not email sending, not DB access. Split concerns into separate classes.
Open for extension, closed for modification. Add new behaviour through subclassing or composition — do not edit working code to add features.
A derived class must be substitutable for its base. If Square extends Rectangle and breaks setWidth, that violates Liskov — do not override to weaken guarantees.
Many small focused interfaces beat one large general one. Clients should not depend on methods they do not use. Keep interfaces lean.
Depend on abstractions, not concretions. Inject dependencies rather than creating them inside a class. Enables testing with mock objects.
The modern cross-platform build system. Generates Makefiles, Ninja files, or IDE projects. CMakeLists.txt describes targets, sources, and dependencies. Industry standard for new C++ projects.
The classic Unix build tool. A Makefile defines rules: how to compile each file and link the final binary. Still widely used, especially on Linux. CMake often generates Makefiles.
XML-based build and dependency manager. pom.xml declares dependencies from Maven Central repository. Downloads and manages JARs automatically. The long-time industry standard.
Groovy or Kotlin DSL build tool. Faster than Maven with incremental builds. Default for Android. Increasingly common in new Java projects. More flexible, less verbose than Maven.
Quiz · Concept Match · Language Classifier
8 questions · test your C++ and Java knowledge
Click a term, then click its matching definition · Find all 8 pairs
Click a concept to select it, then click a zone to assign it · Classify all 12