Skip to content
Posts

cat ~/posts/engineering-fundamentals-interview-refresh.mdx

2026.10.06

14 min read

โ€“โ€“โ€“ views

ibsanju

Engineering Fundamentals Refresh: OOP, SOLID, ACID, CAP and More

A clean, example-first refresher on OOP vs FP, SOLID, ACID, DRY, KISS, CAP, core data structures, design patterns, system design and the STAR method, built for interviews.

Series ยท Part 7 of 7

Interview Essentials

  1. [01]Essential Coding Patterns for Technical Interviews
  2. [02]Essential Coding Patterns for Technical Interviews (Java)
  3. [03]Mastering Java Interviews: Essential Questions and Answers (Basics)
  4. [04]Essential JavaScript Interview Questions and Answers (Basics)
  5. [05]Essential SQL Commands for Developer Interviews
  6. [06]Essential Concepts for Software Testing Quality Engineers (2025)
  7. [07]Engineering Fundamentals Refresh: OOP, SOLID, ACID, CAP and More (you are here)

Interviews love acronyms. SOLID, ACID, DRY, KISS, CAP, STAR. Each one is simple on its own, but under pressure they blur together. This post is the one-page refresh I wish I had before every interview: what each idea means, a small example, and the follow-up question you are likely to get.

Read the cheat sheet first, then jump to whatever feels rusty.

TL;DR cheat sheet

TopicOne lineWhere it applies
OOP vs FPModel the world as objects with state, or as data flowing through pure functionsHow you structure code
SOLIDFive rules for classes that are easy to changeObject-oriented design
ACIDGuarantees that keep a single database correctTransactions in a database
DRYOne piece of knowledge, one placeAny code
KISSThe simplest thing that worksAny code
CAPDuring a network split, choose consistency or availabilityDistributed systems
List, Set, MapOrdered items, unique items, key to valueData structures
Design patternsNamed, reusable solutions to common problemsObject-oriented design
System designRequirements, estimates, design, trade-offsArchitecture interviews
STARSituation, Task, Action, ResultBehavioural interviews

1. OOP vs FP

Both are ways to organise code. Most modern languages, Java and JavaScript included, let you mix them.

Object-oriented programming (OOP)

You group data and the behaviour that changes it into objects. The four pillars:

  • Encapsulation: keep state private and expose only safe operations.
  • Abstraction: show what something does and hide how.
  • Inheritance: reuse and extend behaviour from a parent type.
  • Polymorphism: one interface, many implementations.
interface Shape {
  double area();
}
 
class Circle implements Shape {
  private final double radius;           // encapsulation
  Circle(double radius) { this.radius = radius; }
  public double area() { return Math.PI * radius * radius; }
}
 
class Square implements Shape {
  private final double side;
  Square(double side) { this.side = side; }
  public double area() { return side * side; }
}
 
// polymorphism: the caller does not care which shape it is
double total = shapes.stream().mapToDouble(Shape::area).sum();

Functional programming (FP)

You build programs from pure functions applied to immutable data.

  • Pure functions: same input, same output, no side effects.
  • Immutability: create new values instead of changing existing ones.
  • Higher-order functions: functions that take or return functions (map, filter, reduce).
  • Composition: build big behaviour from small functions.

The same task, "total price of in-stock items", both ways:

// Imperative / OOP style: a loop that mutates a variable
double total = 0;
for (Item item : items) {
  if (item.inStock()) {
    total += item.price();
  }
}
 
// Functional style: describe the result, no mutation
double total = items.stream()
    .filter(Item::inStock)
    .mapToDouble(Item::price)
    .sum();

When to use which? OOP shines when you model long-lived things with state and rules (accounts, orders, UI components). FP shines for transformations and pipelines (data processing, validation, reducers). Pure functions are also far easier to test.

Likely follow-up: "What is the difference between abstraction and encapsulation?" Abstraction hides complexity behind a simple interface; encapsulation hides state behind access control.


2. SOLID

Five principles that keep object-oriented code easy to change.

S: Single Responsibility

A class should have one reason to change.

// Bad: report logic and file saving mixed together
class Report {
  String build() { /* ... */ return ""; }
  void saveToFile(String path) { /* ... */ }
}
 
// Good: each class has one job
class Report { String build() { /* ... */ return ""; } }
class ReportWriter { void save(String content, String path) { /* ... */ } }

O: Open/Closed

Open for extension, closed for modification. Add new behaviour by adding code, not by editing a growing if/else.

interface Discount { double apply(double price); }
 
class NoDiscount implements Discount {
  public double apply(double price) { return price; }
}
class FestiveDiscount implements Discount {
  public double apply(double price) { return price * 0.8; }
}
// A new discount is a new class; existing code stays untouched.

L: Liskov Substitution

A subtype must be usable anywhere its parent is expected without surprises.

The classic trap: a Square that extends Rectangle. Setting the width of a square also changes its height, so code that expects a rectangle breaks. If a child cannot honour the parent's contract, it should not be a child.

I: Interface Segregation

Prefer small, focused interfaces over one large one.

// Bad: a robot is forced to implement eat()
interface Worker { void work(); void eat(); }
 
// Good
interface Workable { void work(); }
interface Eatable { void eat(); }
class Robot implements Workable { public void work() { /* ... */ } }

D: Dependency Inversion

Depend on abstractions, not concrete classes. This is what makes code testable.

interface PaymentGateway { void charge(double amount); }
 
class CheckoutService {
  private final PaymentGateway gateway;          // depends on the interface
  CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
  void pay(double amount) { gateway.charge(amount); }
}
// In tests, pass a fake gateway. In production, pass the real one.

Likely follow-up: "Which SOLID principle helps testing most?" Dependency Inversion, because it lets you inject fakes and mocks.


3. ACID (databases)

ACID describes the guarantees a database gives for a transaction: a group of operations that must succeed or fail together.

LetterMeaningIn one line
AAtomicityAll or nothing
CConsistencyRules and constraints always hold
IIsolationConcurrent transactions do not interfere
DDurabilityOnce committed, it survives a crash

The classic example is a bank transfer:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- If anything fails before COMMIT, ROLLBACK undoes both updates.
  • Atomicity: you never end up with money debited but not credited.
  • Consistency: a constraint like balance >= 0 is never violated.
  • Isolation: another transaction does not see the half-finished transfer.
  • Durability: after COMMIT, a power cut does not lose it.

Isolation levels

Isolation is a dial, traded against performance:

LevelDirty readNon-repeatable readPhantom read
Read Uncommittedpossiblepossiblepossible
Read Committedpreventedpossiblepossible
Repeatable Readpreventedpreventedpossible (by the SQL standard)
Serializablepreventedpreventedprevented

PostgreSQL defaults to Read Committed; MySQL (InnoDB) defaults to Repeatable Read.

Likely follow-up: "Is the C in ACID the same as the C in CAP?" No. ACID consistency means rules and constraints hold. CAP consistency means every read sees the latest write.


4. DRY: Don't Repeat Yourself

Every piece of knowledge should live in one place.

// Repeated: the tax rule lives in two places and will drift
double invoiceTotal = subtotal + subtotal * 0.13;
double quoteTotal   = amount + amount * 0.13;
 
// DRY: one source of truth
static final double TAX_RATE = 0.13;
static double withTax(double amount) { return amount * (1 + TAX_RATE); }

Watch out: DRY is about knowledge, not about code that merely looks similar. Merging two things that only look alike creates the wrong abstraction. A good rule of thumb is the rule of three: duplicate once, abstract on the third time.


5. KISS: Keep It Simple

Choose the simplest solution that works. Simple code is easier to read, test and fix.

// Overthought
boolean isEven(int n) {
  return n % 2 == 0 ? Boolean.TRUE.equals(true) : Boolean.FALSE.equals(true);
}
 
// Simple
boolean isEven(int n) { return n % 2 == 0; }

A close cousin is YAGNI ("You Aren't Gonna Need It"): do not build features or abstractions for a future that may never come.


6. CAP theorem (distributed systems)

When data is copied across several machines, a network partition (machines unable to talk to each other) will eventually happen. During a partition, you can only keep one of:

  • Consistency (C): every read sees the latest write.
  • Availability (A): every request gets a response, even if it might be stale.

Partition tolerance (P) is not optional in a real distributed system, so the real choice is CP or AP:

ChoiceBehaviour during a partitionTypical fit
CPRefuse or delay some requests to stay correctPayments, inventory, leader election
APKeep answering, reconcile laterSocial feeds, shopping carts, view counters

Many databases let you tune this per operation (for example, Cassandra's consistency levels).

The PACELC extension adds the everyday case: if there is a Partition choose A or C, Else choose Latency or Consistency.

Likely follow-up: "Can a system be CA?" Only on a single machine, or if you assume the network never fails, which is not realistic for a distributed system.


7. Data structures: List, Set, Map

These three cover most interview problems. Know what each is for and its cost.

StructureWhat it storesJava implementationsTypical cost
ListOrdered items, duplicates allowedArrayList, LinkedListArrayList: get O(1), contains O(n)
SetUnique itemsHashSet, LinkedHashSet, TreeSetHashSet: add/contains O(1) average; TreeSet: O(log n), sorted
MapKey to value pairs, unique keysHashMap, LinkedHashMap, TreeMapHashMap: get/put O(1) average; TreeMap: O(log n), sorted keys

Quick picks:

  • Need order and index access? ArrayList.
  • Need "have I seen this?" checks? HashSet.
  • Need counts or lookups by key? HashMap.
  • Need things sorted? TreeSet or TreeMap.
  • Need insertion order preserved? LinkedHashSet or LinkedHashMap.

A classic: count word frequency, then find duplicates.

List<String> words = List.of("test", "build", "test", "ship", "build", "test");
 
Map<String, Integer> counts = new HashMap<>();
for (String w : words) {
  counts.merge(w, 1, Integer::sum);          // {test=3, build=2, ship=1}
}
 
Set<String> seen = new HashSet<>();
Set<String> duplicates = new LinkedHashSet<>();
for (String w : words) {
  if (!seen.add(w)) duplicates.add(w);       // add() returns false if present
}
// duplicates = [test, build]

Likely follow-up: "How does a HashMap work?" Keys are hashed into buckets. Collisions share a bucket (a list, which Java turns into a balanced tree when a bucket gets large). That is why equals() and hashCode() must agree.


8. Design patterns (quick refresh)

You rarely need all 23 classic patterns. These five come up most:

PatternProblem it solvesOne-line example
SingletonExactly one shared instanceA config or connection pool
FactoryCreate objects without naming the concrete classShapeFactory.create("circle")
BuilderConstruct complex objects step by stepnew User.Builder().name("A").age(30).build()
StrategySwap an algorithm at runtimeDifferent discount or sorting rules
ObserverNotify many listeners when something changesEvent listeners, pub/sub

Strategy is the one to be able to write from memory, and it is the Open/Closed principle in action:

interface SortStrategy { void sort(int[] data); }
 
class QuickSort implements SortStrategy { public void sort(int[] d) { /* ... */ } }
class MergeSort implements SortStrategy { public void sort(int[] d) { /* ... */ } }
 
class Sorter {
  private SortStrategy strategy;
  Sorter(SortStrategy strategy) { this.strategy = strategy; }
  void setStrategy(SortStrategy s) { this.strategy = s; }
  void sort(int[] data) { strategy.sort(data); }
}

9. System design (SD)

System design interviews are less about one correct answer and more about how you think. A repeatable structure:

  1. Clarify requirements: functional (what it does) and non-functional (scale, latency, availability).
  2. Estimate: users, requests per second, storage. Rough numbers are fine.
  3. Define the API: the main endpoints and their inputs and outputs.
  4. Model the data: entities, and SQL vs NoSQL with a reason.
  5. High-level design: clients, load balancer, services, database, cache, queues.
  6. Deep dive: the hardest part (scaling reads, hot keys, consistency).
  7. Trade-offs: what you chose, what you gave up, and why.

A tiny example, a URL shortener:

  • API: POST /shorten with a long URL returns a short code; GET /:code redirects.
  • Data: a table of code, longUrl, createdAt.
  • Design: generate a short unique code (for example, base62 of an ID), store it, and put a cache in front of reads because redirects are read-heavy.
  • Trade-off: reads vastly outnumber writes, so optimise the read path; brief staleness of rarely changed data is acceptable, which leans AP for the cache layer.

The building blocks to name confidently: load balancer, cache (Redis), CDN, database replicas and sharding, message queue (Kafka, SQS), rate limiting.


10. STAR method (behavioural questions)

For "Tell me about a time whenโ€ฆ" questions, answer with STAR:

  • Situation: the context, briefly.
  • Task: what you were responsible for.
  • Action: what you did, specifically. Spend most of the time here.
  • Result: the outcome, with numbers if possible, and what you learned.

A template example for "Tell me about a time you improved a process":

Situation: Our end-to-end suite was flaky and failed on roughly one in three runs, so the team had stopped trusting it.

Task: I was asked to make the suite reliable before the next release.

Action: I grouped failures by cause, replaced fixed sleeps with explicit waits, isolated test data per run, and added retries only for known network-dependent steps.

Result: Flaky failures dropped sharply, the team started using the suite as a release gate again, and I documented the patterns for new tests.

Tips: prepare four or five stories in advance (a challenge, a failure, a conflict, leadership, a big win) and reuse them across questions.


Rapid-fire recap

  • OOP vs FP? Objects with state and behaviour, versus pure functions on immutable data.
  • SOLID in one breath? Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion.
  • ACID? Atomicity, Consistency, Isolation, Durability: correctness for one database.
  • CAP? During a partition, choose consistency or availability.
  • DRY vs KISS? Do not duplicate knowledge; do not overcomplicate.
  • List vs Set vs Map? Ordered items, unique items, key-value lookups.
  • System design? Requirements, estimates, API, data, design, deep dive, trade-offs.
  • STAR? Situation, Task, Action, Result, with most of the time on Action.

This post is part of the Interview Essentials series. If you want to go deeper, the earlier parts cover coding patterns, Java and JavaScript basics, SQL and testing concepts.

Enjoyed this? Leave a reaction

Share this article

Comments

Loading commentsโ€ฆ

Leave a comment

Enjoying this post?

Don't miss out ๐Ÿ˜‰. Get an email whenever I post, no spam.

Subscribe Now