← All sheets Software Engineering

Software Engineering Cheat Sheet (general, for any interview)

Version: 2 (all technical questions from the other files now included here). Date: 2026-11-13. This is the main reference. It holds all technical questions from full_stack_interview_questions.md, QinetiQ/qinetiq_interview_prep.md and NATS/nats_interview_prep.md, plus OOP. Company files keep only company-specific content (stories, values, role).

Three parts:

Format:

Word: simple explanation. Example: real software example. Your link: a line from your own CV/work that you can say.

The answer formula for any "What is X?" question:

1. One-line definition → 2. A real example → 3. Why it matters (benefit) → 4. One trade-off → stop

Example: "Encapsulation means an object hides its data and allows changes only through its methods. For example, a credit card object keeps its balance private and changes it only through charge(), which checks the limit. It protects the data from invalid changes. The trade-off is a bit more code."


PART A: SOFTWARE

A1. Object-Oriented Programming (OOP)

OOP: build software from objects. Each object holds data (fields) and behaviour (methods) together. Class: the template. Object: one instance made from it. Example: Card class → your card object. Constructor: runs when an object is created; sets the starting values.

class Card {
  constructor(public owner: string) {} // constructor sets the start values
}
const myCard = new Card("Alwin"); // object = one instance of the class

One example for all of OOP: a checkout with one "Pay" button. Underneath, the payment can be a card, PayPal or Apple Pay. The checkout only calls pay().

Method vs function

Function: a block of code that stands alone. It is not part of a class. Method: a function that belongs to a class or object. It can use the object's data through this.

function addVat(amount: number) {
  // FUNCTION: stands alone, uses only its inputs
  return amount * 1.2;
}

class Card {
  private balance = 0;
  charge(amount: number) {
    // METHOD: belongs to Card, uses this.balance
    this.balance += addVat(amount);
  }
}

addVat(10); // call a function by its name
myCard.charge(10); // call a method ON an object
FunctionMethod
Belongs toNothing (stands alone)A class / object
Called asaddVat(10)myCard.charge(10)
Can use object data (this)NoYes

Constructor and super

Constructor: a special method that runs once, when the object is created (new). It sets the starting values. super: a way for a child class to reach its parent class.

  • super(...) in the child's constructor = run the parent's constructor first.
  • super.method() = call the parent's version of a method (often inside an override, to add to it, not replace it).

extends below means "CreditCard is a child of Card" (inheritance, pillar 3).

class Card {
  constructor(protected owner: string) {} // parent sets up "owner"

  pay(amount: number) {
    console.log(`${this.owner} paid ${amount}`);
  }
}

class CreditCard extends Card {
  private limit: number;

  constructor(owner: string, limit: number) {
    super(owner); // 1. parent constructor runs first: sets owner
    this.limit = limit; // 2. then the child sets its own data
  }

  pay(amount: number) {
    if (amount > this.limit) throw new Error("Over limit"); // child adds a check
    super.pay(amount); // then reuses the parent's pay()
  }
}

const card = new CreditCard("Alwin", 1000); // constructors run: Card first, then CreditCard
card.pay(50); // CreditCard.pay → check → Card.pay

this.pay() or super.pay()? Second example, with no override:

class Card {
  pay(amount: number) {
    console.log(`Paid ${amount}`);
  }
}

class DebitCard extends Card {
  // no pay() here: DebitCard inherits Card's pay()

  payWithFee(amount: number) {
    this.pay(amount + 1); // ✔ finds pay() on the parent through inheritance
  }
}

new DebitCard().payWithFee(50); // "Paid 51"
CallMeaningWhen to use
this.pay()"Use this object's pay()": the child's version if it has one, if not the parent'sNormal choice, anywhere in the parent or child
super.pay()"Skip the child, use the parent's version"Only inside a child class, mainly in an override that wants to add to the parent's version

Does the parent constructor run automatically?

When you create a child object, the parent constructor must run first, so the parent part of the object is set up. Who starts it depends on one thing: does the child have its own constructor?

class Card {
  constructor() {
    console.log("Card set up");
  }
}

// Case 1: child has NO constructor
class CreditCard extends Card {}
new CreditCard(); // prints "Card set up" → parent constructor ran automatically

// Case 2: child HAS a constructor → you must start the parent yourself
class DebitCard extends Card {
  constructor() {
    super(); // runs Card's constructor first → "Card set up"
    console.log("Debit set up");
  }
}
new DebitCard(); // prints "Card set up", then "Debit set up"

// Case 3: child HAS a constructor but forgets super()
class GiftCard extends Card {
  constructor() {
    console.log("Gift set up"); // TS/JS: ERROR, parent constructor never started
  }
}

Order when you create a child object:

new CreditCard(...)  →  super(owner): Card constructor runs  →  CreditCard constructor continues

Rules to know:

Constructor overloading: several constructors with different parameters (C++, Java, C#). Example: Card(owner) and Card(owner, limit). TS/JS allow only one constructor (use optional parameters instead).

The 4 pillars

1. Encapsulation: hide the data, control the access

Encapsulation: keep data private; change it only through methods that apply the rules. Example: a card's balance is private. Nobody can set it directly. Only charge() can change it, and it checks the limit. The card number is hidden; only the last 4 digits are shown.

class Card {
  private balance = 0; // hidden: no direct access

  charge(amount: number) {
    // the only way in, with a rule
    if (amount > 1000) throw new Error("Over limit");
    this.balance += amount;
  }
}

Why: data stays valid; rules live in one place; sensitive data cannot leak. Your link: "Other services cannot write to my notifications queue directly. They call the API, which validates the request."

2. Abstraction: show what, hide how

Abstraction: give a simple interface; hide the complex details. The caller knows what it does, not how. Example: the checkout calls pay(50). How it pays is hidden inside pay(): check the card details, contact the bank, wait for approval, record the payment. The checkout never sees these steps. It only gets back "paid" or "declined".

class CardPayment {
  pay(amount: number) {
    // the caller only sees this one method
    this.checkCardDetails(); // ┐
    this.askBankToApprove(amount); // │ HOW it pays: hidden inside
    this.recordPayment(amount); // ┘
  }
}

checkout.pay(50); // caller knows WHAT (pay £50), not HOW

Same idea in real life: you turn the steering wheel; you do not move the steering rack and wheels yourself.

Encapsulation vs abstraction: encapsulation hides the data; abstraction hides the complexity. Your link: "The UI calls one notifications endpoint. It does not know about the template, the queue or the email service."

3. Inheritance: a child inherits the parent's properties and methods

Inheritance: a child class inherits the parent's properties and methods. It can add its own extra properties and methods, and change (override) inherited methods. An "is-a" relation. Example: a credit card is a card; a debit card is a card. Shared steps (check expiry) are written once in Card.

A child can do 3 things with what it inherits:

class Card {
  owner = "";
  checkExpiry() {
    /* shared by all cards */
  }
  pay(amount: number) {
    console.log(`Paid ${amount}`);
  }
}

class CreditCard extends Card {
  // 1. INHERIT: owner, checkExpiry() and pay() come for free, nothing to write

  // 2. ADD: something only credit cards have
  limit = 1000;
  payInInstalments(amount: number, months: number) {
    /* credit cards only */
  }

  // 3. CHANGE (override): its own version of a parent method
  pay(amount: number) {
    if (amount > this.limit) throw new Error("Over limit"); // extra rule
    super.pay(amount); // then the parent's pay()
  }
}

class DebitCard extends Card {} // only INHERITS: uses Card's pay() as it is

const credit = new CreditCard();
credit.checkExpiry(); // inherited from Card
credit.payInInstalments(300, 3); // added in CreditCard
credit.pay(50); // changed in CreditCard (limit check first)
new DebitCard().pay(50); // Card's original pay()
What the child doesExample
InheritUses the parent's property/method as it ischeckExpiry(), owner
AddNew property/method the parent does not havelimit, payInInstalments()
ChangeOverrides a parent method with its own versionpay() with a limit check

Risk: a change in the parent affects every child. Prefer composition (an object has other objects and uses them, "has-a"; see "Composition vs inheritance" below) unless it is a true "is-a".

4. Polymorphism: same call, different behaviour

Polymorphism: one method call does different things, depending on the real object type. Example: the same pay() call charges a card for one customer and opens PayPal for another. Adding Google Pay is one new class; the checkout does not change.

// 1. The parent: every payment method MUST have pay()
abstract class PaymentMethod {
  abstract pay(amount: number): void; // no body here: each child writes its own
}

// 2. Children: same method name, different behaviour
class CardPayment extends PaymentMethod {
  pay(amount: number) {
    console.log(`Charged £${amount} to card`);
  }
}

class PayPalPayment extends PaymentMethod {
  pay(amount: number) {
    console.log(`Sent to PayPal to pay £${amount}`);
  }
}

class ApplePayPayment extends PaymentMethod {
  pay(amount: number) {
    console.log(`Asked phone to approve £${amount}`);
  }
}

// 3. The checkout only knows "PaymentMethod", not which one it is
function checkout(method: PaymentMethod) {
  method.pay(50); // same call, different behaviour
}

checkout(new CardPayment()); // "Charged £50 to card"
checkout(new PayPalPayment()); // "Sent to PayPal to pay £50"
checkout(new ApplePayPayment()); // "Asked phone to approve £50"

How it works:

checkout(method) → method.pay(50) → at run time, the program looks at the REAL object
      → CardPayment? run its pay()   PayPalPayment? run its pay()   ApplePayPayment? run its pay()

extends or implements? Both give polymorphism, but they mean different things:

Use extends when the classes are the same kind of thing and share real code. Example: CreditCard and DebitCard are both cards and both need the same checkExpiry(). Write it once in Card, and both extends Card.

Use implements when different classes only need to do the same job, but do it in their own way, with no shared code. Example: CardPayment, PayPalPayment and BankTransfer can all refund, but each refund works differently. So each one implements Refundable and writes its own refund().

Simple test: do the classes need to share the same code? Yes (both cards use the same checkExpiry()) → extends. No, they only need the same method name, each with its own code (every payment has refund(), but each refund works differently) → implements.

Interface in one example: a capability like "can refund".

interface Refundable {
  refund(amount: number): void; // only the method's name and shape (its inputs and return type): no body, no code
}
class CardPayment implements Refundable {
  refund(amount: number) {
    console.log(`Refunded £${amount}`);
  } // the class writes the code
}

If CardPayment forgets refund(), the compiler gives an error: it broke its promise.> Note: "interface" also has a general meaning: the public part of anything that others use (e.g. an API is the interface of a service). In OOP questions, it means the keyword above.

Bigger example: one parent class + several capabilities:

// ONE parent class: shared code for every payment
abstract class PaymentMethod {
  abstract pay(amount: number): void;
  logPayment(amount: number) {
    console.log(`Logged £${amount}`);
  } // shared code, inherited
}

// CAPABILITIES (interfaces): no code, just promises
interface Refundable {
  refund(amount: number): void;
} // "can give money back"
interface Recurring {
  payMonthly(amount: number): void;
} // "can take a payment every month"

// Card: extends ONE class + implements TWO interfaces
class CardPayment extends PaymentMethod implements Refundable, Recurring {
  pay(amount: number) {
    console.log(`Card charged £${amount}`);
  }
  refund(amount: number) {
    console.log(`Card refunded £${amount}`);
  }
  payMonthly(amount: number) {
    console.log(`Card subscription £${amount}/month`);
  }
}

// PayPal: can refund, but no monthly payments here
class PayPalPayment extends PaymentMethod implements Refundable {
  pay(amount: number) {
    console.log(`PayPal paid £${amount}`);
  }
  refund(amount: number) {
    console.log(`PayPal refunded £${amount}`);
  }
}

// Gift card: can only pay (no refunds, no subscriptions)
class GiftCardPayment extends PaymentMethod {
  pay(amount: number) {
    console.log(`Gift card used £${amount}`);
  }
}

// Each function asks only for the capability it needs
function checkout(method: PaymentMethod) {
  method.pay(50);
  method.logPayment(50);
}
function processReturn(method: Refundable) {
  method.refund(20);
}
function startSubscription(method: Recurring) {
  method.payMonthly(10);
}

checkout(new GiftCardPayment()); // ✔ every payment can pay
processReturn(new PayPalPayment()); // ✔ PayPal is Refundable
startSubscription(new CardPayment()); // ✔ Card is Recurring
processReturn(new GiftCardPayment()); // ✘ compile error: gift card is not Refundable

What this shows:

extends PaymentMethodimplements Refundable, Recurring
How manyOneMany
Gets code?✔ logPayment() comes for free✘ the class writes refund() itself
Meaning"Card is a payment method""Card can refund and can repeat monthly"

Other languages:

TypeDecidedHow
Run-time (dynamic)While running: the program looks at the real objectOverriding (C++ needs virtual)
Compile-time (static)When building: the compiler picks the version from the argument typesOverloading, operator overloading, templates/generics

Your link: "One template per notification type. The processor does the same 'render and send' for every type."

Access modifiers: public, private, protected

Access modifier: says who can use a field or method. The tool for encapsulation.

ModifierWho can use itExample
publicAnyonepay()
privateOnly this classbalance, card number
protectedThis class + subclassesa shared checkExpiry() that subclasses can use and change, but the checkout cannot
class Card {
  public pay() {} // anyone
  protected checkExpiry() {} // Card + subclasses
  private balance = 0; // Card only
}

class CreditCard extends Card {
  test() {
    this.pay(); // ✔ public: allowed everywhere
    this.checkExpiry(); // ✔ protected: allowed in a subclass
    this.balance; // ✘ private: compile error
  }
}

const card = new CreditCard();
card.pay(); // ✔ public: outside code can call it
card.checkExpiry(); // ✘ protected: outside code cannot

Can a subclass change a protected method, or only use it? Both. Protected means a subclass can call it and override it with its own version:

class DebitCard extends Card {
  protected checkExpiry() {
    /* own version for debit cards */
  } // ✔ override
}

Rule to say: "Start with everything private. Open up only what is needed. The public part is a promise to other code."

LanguageNote
TypeScriptprivate is checked only when compiling; in the final JavaScript anyone can still read it. #field is real privacy, even at run time. Default: public
C++class members private by default; struct public by default. friend can see private members
JavaAlso package-private: no keyword = visible only inside the same package (folder of classes)
PythonNo real privacy. A leading _name just means "internal, please do not use"

Overloading

Overloading: several methods with the same name in the same class, but different inputs (number or type of parameters). The compiler picks the right one from what you pass in. Example: refund(id) gives a full refund; refund(id, amount) gives a partial refund.

void refund(int id);              // full refund
void refund(int id, int amount);  // partial refund: same name, extra input

refund(42);       // calls the first one
refund(42, 500);  // calls the second one

Operator overloading

Operator overloading: the same idea, but for operators (+, ==, <) instead of method names. You give an operator a custom meaning for your own class. Example: add two Money values with a + b.

Money operator+(const Money& other) const {
  return Money(pence + other.pence);  // "+" now adds two Money objects
}

Abstract classes and abstract methods

Abstract class: a class that is only a base for other classes. You cannot create an object from it directly. It exists to share code and to set rules for its children. Abstract method: a method with no body in the parent. Every child must write its own version.

abstract class PaymentMethod {
  constructor(protected owner: string) {} // ✔ can have a constructor

  abstract pay(amount: number): void; // abstract: no body, child MUST write it

  logPayment(amount: number) {
    // normal method: real code, inherited
    console.log(`${this.owner} paid £${amount}`);
  }
}

class CardPayment extends PaymentMethod {
  pay(amount: number) {
    console.log("Card charged");
  } // ✔ writes the abstract method
}

class BrokenPayment extends PaymentMethod {} // ✘ compile error: pay() is missing

new PaymentMethod("Alwin"); // ✘ compile error: cannot create an abstract class
new CardPayment("Alwin").logPayment(50); // ✔ create the child; it inherits logPayment()

Rules to remember:

In other languages:

Interface vs abstract class

Interface: a contract; lists methods, no code. A class can implement many. Abstract class: a parent you cannot create directly; can have some code. A class extends only one.

InterfaceAbstract class
Code inside?NoYes
How many?ManyOne
Use whenUnrelated classes share a capability (Refundable)Related classes share code (Card with checkExpiry())

C++ has no interface keyword: use a class with only pure virtual functions (virtual void pay() = 0;).

Composition vs inheritance

Composition: a class keeps another object in one of its fields and calls that object's methods to do part of the work. "Has-a" = "has one of these in a field". Example: a PaymentService has a fraud checker in a field. Before each payment, it asks the fraud checker "is this safe?". A payment service is not a kind of fraud checker; it uses one.

It does not mean a class written inside another class (that is a "nested class", a different thing). Both classes are written separately; one just holds the other.

interface FraudChecker {
  // an interface: any fraud checker must have check()
  check(amount: number): boolean;
}

class SimpleFraudChecker implements FraudChecker {
  check(amount: number) {
    return amount < 1000;
  } // a real checker
}

class PaymentService {
  private fraud: FraudChecker; // 1. a private FIELD that holds a fraud checker

  constructor(fraud: FraudChecker) {
    // 2. the checker is created OUTSIDE and passed in
    this.fraud = fraud; // 3. store it in the field
  }

  pay(amount: number) {
    if (!this.fraud.check(amount)) throw new Error("Blocked"); // 4. USE it: composition
    console.log(`Paid £${amount}`);
  }
}

const service = new PaymentService(new SimpleFraudChecker()); // real one in the app
const testService = new PaymentService({ check: () => true }); // fake one in a test

TypeScript shortcut (parameter property): writing private in front of a constructor parameter creates the private field and stores the value for you. These two are the same:

// long form (as above)
private fraud: FraudChecker;
constructor(fraud: FraudChecker) { this.fraud = fraud; }

// short form: same result, one line
constructor(private fraud: FraudChecker) {}

Other languages (Java, C#, C++) have no shortcut: you write the field and the assignment yourself.

Why prefer it: swap parts easily, less coupling, no deep class trees. Rule: inheritance for is-a (credit card is a card); composition for has-a (service has a fraud checker).

What else can a class have?

Short list of class features you may be asked about:

class Card {
  static count = 0; // STATIC: belongs to the class, shared by all cards
  readonly number: string; // READONLY: set once, never changed
  #pin = "1234"; // PRIVATE (#): real hidden field
  private _limit = 1000;

  constructor(number: string) {
    // CONSTRUCTOR: runs on "new"
    this.number = number;
    Card.count++; // static used through the class name
  }

  get limit() {
    return this._limit;
  } // GETTER: read it like a property: card.limit
  set limit(value: number) {
    // SETTER: write it like a property, with a rule
    if (value < 0) throw new Error("Limit cannot be negative");
    this._limit = value;
  }

  static isValid(number: string) {
    // STATIC METHOD: call on the class: Card.isValid(...)
    return number.length === 16;
  }

  describe() {
    return "Card";
  } // normal method
}

class CreditCard extends Card {
  override describe() {
    return "Credit card";
  } // OVERRIDE keyword: "this replaces a parent method"
}

class Box<T> {
  // GENERIC class: works with any type T
  constructor(public item: T) {}
}
const cardBox = new Box<Card>(new Card("4111111111111111"));
FeatureSimple meaningExample
static property / methodBelongs to the class, not one objectCard.count, Card.isValid()
readonly (C++ const, Java final field)Set once, never changednumber
Getter / setter (get / set)Read/write like a property, but with code and rulescard.limit = 2000 runs the check
override keywordMarks a method that replaces a parent's. The compiler checks the parent really has itoverride describe()
final (Java, C++) / sealed (C#)Stops overriding or stops other classes extending this one. TypeScript has no finalfinal class Card
Private constructorNobody outside can use new; the class controls creation (Singleton = only one object ever; factory = a method that builds objects for you)Card.create(...)
Generic classOne class that works with many types; T is a placeholder for the real typeBox<Card>, Box<string>
Nested / inner classA class inside another class (Java, C#, C++)Order.Item
Destructor (C++)Runs when the object is destroyed; frees resources it holds, e.g. memory or a file (RAII, see B2)~Card()
Friend (C++)Lets a named class/function see private membersfriend class Auditor;

Other OOP terms

TermMeaning
staticBelongs to the class, not one object
this / superThe current object / the parent class (see "Constructor and super")
Getter / setterControlled read / write: a method runs, so it can check rules (e.g. limit cannot be negative)
ImmutableCannot change after creation (a past transaction)
CouplingHow much classes depend on each other. Want low: a change in one class should not break many others
CohesionHow focused a class is on one job. Want high: everything in it belongs together
Multiple inheritanceTwo parents (C++ allows it)
Diamond problemB and C both inherit from A; D inherits from B and C. If both changed a method from A, which one does D get? (C++ fixes it with virtual inheritance)
Virtual function (C++)A method a child can override; the right version is picked at run time using the vtable (a hidden table, per class, that points to the correct method)
Virtual destructor (C++)Needed in a base class. Without it, delete through a parent pointer runs only the parent's clean-up and the child's part leaks
Dependency injectionPass the things a class needs (database, gateway) in through the constructor, instead of creating them inside. Then tests can pass in fakes

OOP vs functional

30-second answer: "Explain the 4 pillars"

"Take a checkout with one Pay button. Encapsulation: a card keeps its balance private and only charge() can change it, with the limit check. Abstraction: the checkout only calls pay(); how it pays (check the card, ask the bank, record it) is hidden inside. Inheritance: credit and debit cards extend a Card class, so shared checks are written once. Polymorphism: the same pay() call charges a card or opens PayPal, so adding Google Pay is one new class with no change to the checkout. For has-a relations, like a fraud checker, I prefer composition."

30-second answer: "public vs private vs protected?"

"Public: anyone can use it, like pay(). Private: only that class, like the balance. Protected: the class and its subclasses, like a shared expiry check that a subclass can use or override, but outside code cannot call. I start private and open up only what is needed. In TypeScript, private is compile-time only; #field is real privacy. In C++, class members are private by default."


A2. SOLID and design patterns

SOLID (5 design rules)

LetterRuleSimple meaningPayment example
SSingle ResponsibilityOne class, one reason to changeFraudChecker only checks fraud; ReceiptMailer only sends receipts
OOpen/ClosedOpen to extension, closed to changeAdd ApplePay as a new class; do not edit the checkout
LLiskov SubstitutionA child must work anywhere the parent works, with no surprisesAny PaymentMethod can go into the checkout; a child must not throw "not supported" for pay()
IInterface SegregationMany small interfaces, not one big oneRefundable separate from PaymentMethod (gift cards cannot refund)
DDependency InversionDepend on interfaces, not concrete classes ("concrete" = a real, specific class)PaymentService depends on a CardGateway interface, not on StripeGateway, so you can swap the provider

Your link: "My notifications design follows S and O: each service has one job, and a new notification type is a new template, not a code change."

Common design patterns

PatternSimple meaningExample
SingletonOnly one instance existsOne database connection pool. Risk: hidden global state, hard to test
FactoryA method creates the correct object type for youPaymentFactory.create("paypal") returns a PayPal object
StrategySwap an algorithm (a way of doing a task) at run timeDifferent fee rules per card type, chosen when the payment runs
Observer / pub-subObjects subscribe ("tell me when X happens"); when the event is published, all subscribers react"Payment succeeded" → receipt service, loyalty points, analytics all react. Your link: your notifications and Agenda queue
AdapterWrap one interface to look like anotherWrap an old bank API so it fits your CardGateway interface. Your link: legacy modernisation
DecoratorAdd behaviour by wrapping an object, without changing its classWrap a gateway so every call is also logged or retried
RepositoryHide database access behind a class, so the rest of the code never writes SQL directlyTransactionRepository.findById()
Middleware / chain of responsibilityA request passes through a chain of stepsYour link: Express middleware: logger → auth → validate → handler

A3. Errors and exceptions

Exception:     throw → jumps up the call stack → caught by try/catch higher up
Return value:  function returns an error/result → the caller checks it at once
PositivesNegatives
Cannot be silently ignored (uncaught → program stops)Hidden control flow: any call can jump out, and the code does not show it
Main code stays clean (errors handled in one catch)Slow, timing not predictable (bad for real-time)
Travels up many levels with no extra codeBigger code size (matters on small embedded chips)
Carries a message + stack trace (the list of calls that led to the error)Resource leaks if careless: a file stays open. Fix: RAII (C++) or finally

A4. Data structures and Big O

StructureGood forPayment example
Array / listOrdered items; get item N instantly, O(1)List of transactions
Hash map (JS Map / object)Key → value; find by key instantly, O(1)Card ID → card details
SetUnique items, fast "contains?"Seen idempotency keys
Queue (FIFO = first in, first out)Process in arrival orderPayment jobs; your Agenda queue
Stack (LIFO = last in, first out)Undo, the call stackUndo steps in a form
TreeSorted data, parent → child levelsDatabase indexes (a B-tree keeps keys sorted for fast search)
GraphItems (nodes) and the links between them (edges)Fraud: cards linked by the same device

Big O: how run time grows when the input grows (n = number of items). O(1) same time for any size (hash lookup) · O(log n) halve the work each step (binary search) · O(n) one loop over all items · O(n log n) a good sort · O(n²) a loop inside a loop (1,000 items → 1,000,000 steps).


A5. Concurrency and threads (software view)

Concurrency: many tasks in progress at once, taking turns. Example: one chef cooks 3 dishes, switching between them. Parallelism: many tasks at the same instant on many cores. Example: 3 chefs, 3 dishes.

Process: a running program with its own memory. Thread: a path of execution inside a process; threads share memory.

Race condition: two threads change shared data at the same time. Example: two payment requests read balance £100, both charge £80, both write £20. You lost £80. Fix: a lock (mutex: one thread at a time), an atomic operation (done in one step), a database transaction, or optimistic locking (see A7).

Deadlock: two threads wait for each other forever. Fix: take locks in the same order.

JavaScript: your code runs on one thread (the event loop). Slow I/O (input/output: database, network, files) is handed off, so your thread does not wait:

Concurrency (1 core):   A ▮▮ B ▮▮ A ▮▮ C ▮▮ B ▮▮   ← tasks take turns
Parallelism (3 cores):  A ▮▮▮▮▮▮
                        B ▮▮▮▮▮▮                   ← tasks run together
                        C ▮▮▮▮▮▮
ProcessThread
MemoryOwnShared with other threads
Cost to startHighLow
Talk to othersMessages, pipes, sockets (channels between programs)Shared variables (needs locks)
One crashesOthers keep runningCan crash the whole process
Single-threadedMultithreaded
GoodSimple, no shared-memory races, easy to debugUses all cores; one slow task does not stop others
BadOne slow task blocks all; one core onlyRaces, deadlocks; harder to test

Thread-safe: works correctly when many threads use it at once. Semaphore: a counter that limits users of a resource. Example: max 10 connections to the card gateway.

JavaScript in more detail:

Your JS code (1 thread: the event loop)
      ↓ slow I/O (database, network, files)
Browser (front end) or Node (back end) does the slow work
      ↓ done
Callback → queue → event loop runs it when the stack is empty
app.get("/statement", (req, res) => {
  const pdf = buildHugeStatementPdf(); // 5 s of CPU → ALL users wait 5 s
  res.send(pdf);
});

How to make JS parallel:

OptionWhat it isWhen
Worker threads (Node)Real threads, own event loop + memory, talk by messagesCPU-heavy work
Web Workers (browser)Background thread, no DOM accessHeavy work that would freeze the UI
Cluster (Node)Starts one copy of your server per CPU core; they share one port (e.g. 3000)Use all cores for a server
Child processesRun another programScripts, tools
More containersRun more copies of the service (horizontal scaling) in KubernetesProduction
SharedArrayBuffer + AtomicsMemory that workers share; Atomics makes changes safeRare, fast data sharing
const worker = new Worker("./heavy.js"); // heavy work runs on another thread
worker.on("message", (result) => use(result)); // main thread stays free meanwhile

30-second answer: "Is JS single-threaded? How do you make it multithreaded?"

"My JS code runs on one thread with an event loop. Node passes slow I/O to the OS and a small thread pool, so one thread serves many requests. CPU-heavy work blocks every request, so I move it to worker threads or a worker pool. To use all cores for a server, I use the cluster module or more containers in Kubernetes. Async/await does not make code parallel; it only stops the thread from waiting."

30-second answer: "Concurrency vs multithreading?"

"Concurrency means many tasks are in progress at once; they can take turns on one core, like Node's event loop. Multithreading is one way to do it: threads share memory and can run in parallel on many cores. It is faster for CPU work, but shared memory brings races and deadlocks, so I protect shared data with locks or atomics, or avoid sharing and pass messages."

Distributed concurrency (your world):

ProblemFix
Two updates overwrite each otherTransactions, or optimistic locking (each record has a version number; save only if it did not change)
A retry creates a second chargeIdempotency key (a unique ID per attempt; the server ignores repeats, see A6)
Too many requests at onceA queue
Two workers take the same jobJob locking (Agenda locks jobs in MongoDB)

Your link: "Two copies of a service can update the same record. I use transactions, optimistic locking, idempotent APIs, and a queue. Agenda locks each job in MongoDB so two workers do not take the same one."


A6. APIs

REST: each resource has a URL; HTTP methods act on it. Example: GET /payments/123.

MethodActionIdempotent?
GETReadYes
POSTCreateNo
PUTReplaceYes
PATCHChange partNot always
DELETERemoveYes

Idempotent: sending the same request many times has the same effect as once. Example: a customer clicks "Pay £50" and the network drops. The app retries. Without idempotency → charged twice. With an idempotency key (a unique ID per payment attempt), the server sees the same key and returns the first result → charged once.

POST /payments   Idempotency-Key: 7f3a-...   → charge £50, store key + result
POST /payments   Idempotency-Key: 7f3a-...   → key seen → return stored result, no new charge

Your link: "My notifications jobs use the same idea: a unique ID per notification, checked before sending, so a retry does not send two emails."

StatusMeaningPayment example
200 / 201OK / createdPayment created
202Accepted: request received, the work happens laterPayment queued (your link: notifications endpoint)
400Bad requestInvalid card number format
401 / 403Not logged in / no permissionNot your account
404Not foundUnknown transaction
409Conflict with the current stateDuplicate payment
422Valid format, business rule failedCard expired
429Too many requestsRate limit on login attempts
500 / 503Server error / unavailableBank gateway down

A7. Databases

SQL (PostgreSQL)NoSQL (MongoDB)
ShapeTables with a fixed schema (rules for columns)Flexible JSON-like documents
RelationsStrong: joins combine tablesWeak: data is often copied into each document
Best forPayments, accounts, ledgersChanging data, logs, your Agenda jobs

ACID: Atomicity (all or nothing), Consistency (rules always hold), Isolation (parallel transactions do not mix), Durability (saved data survives a crash). Example: a transfer: take £100 from account A and add £100 to account B. If step 2 fails, step 1 is undone.


A8. Security

TopicOne line
Authentication vs authorisationWho are you (log in) vs what are you allowed to do (permissions)
Session vs JWTSession: the server stores the login and the browser keeps only an ID. JWT: a signed token that holds the user info; the server just checks the signature. A JWT is hard to cancel before it expires → keep expiry short
OAuthLet an app use your account elsewhere without sharing your password ("Log in with Google")
XSS (cross-site scripting)Attacker puts a script into your page (e.g. in a comment) and it runs in other users' browsers. Fix: escape output (show <script> as text), CSP (a header that limits which scripts can run). React escapes by default
CSRF (cross-site request forgery)A bad site makes your browser send a request to your bank, and the browser adds your login cookie. Fix: SameSite cookies (not sent from other sites), CSRF tokens (a secret value the bad site cannot know)
CORSBrowser rule: a page on site A cannot read responses from site B unless B allows it with a header (Access-Control-Allow-Origin)
PasswordsNever store plain text. Use a slow hash (one-way scramble) + salt (random extra data per user, so equal passwords look different). Tools: bcrypt, Argon2
SecretsAPI keys, passwords: keep in a secret store (e.g. AWS Secrets Manager, Kubernetes Secret), never in Git or front-end code
Least privilegeEach user/service gets only the access it needs
PCI DSSSecurity rules for anyone who handles card data. Never store the full card number or CVV yourself; use tokenisation

Tokenisation: replace the real card number with a random token. Only the payment provider can map it back.

TLS

TLS: encrypts data between two computers. HTTPS = HTTP + TLS. Gives confidentiality, integrity, authentication.

Client hello → server sends certificate → client checks it (trusted CA, name, expiry)
      → both agree a session key → all data encrypted with that key

A9. JavaScript core

QuestionShort answerExample
let vs const vs varlet: visible only inside its { } block, can change. const: same, but cannot be reassigned. var: visible in the whole function, old and error-prone; do not useconst total = calcTotal(basket)
== vs ===== converts types first ("5" == 5 is true). === checks value and type. Always use ===if (status === "approved")
Truthy / falsyFalsy: false, 0, "", null, undefined, NaN. Everything else is truthyamount || 10 replaces a real 0 with 10 → use amount ?? 10
null vs undefinedundefined: never set. null: set to "no value" on purposecard.expiry === undefined (missing field)
ScopeWhere a variable is visibleA let in an if block is not visible outside
HoistingJS registers declarations before running the code. Using a var too early gives undefined; using let/const too early throws an errorconsole.log(x); let x = 1; → error
thisThe object that calls the function. An arrow function (() => {}) has no this of its own: it uses the this of the code around itcard.charge() → this is card
Inside charge(): setTimeout(() => this.save(), 100) → this is still card. With function () {} instead, this is not card any more (the timer calls it, not card)
PrototypeEach JS object has a hidden link to another object (its prototype). If a property is not found, JS looks there, then the next one (the "chain"). class is nicer syntax on top of thisCreditCard.prototype.charge
Spread / destructuringSpread (...) copies items into a new array/object. Destructuring takes values out into variables{ ...card, limit: 2000 }, const { last4 } = card
map / filter / reduceTransform / select / combine an arraypayments.filter(p => p.ok).reduce((s, p) => s + p.amount, 0)
Shallow vs deep copyShallow (spread) copies only the top level; objects inside are still shared with the original. Deep copies everything. Use structuredClone(x) for deepChange copy.address.city after a spread → original changes too

Pass by value vs pass by reference: what a function gets when you pass it a variable.

  • By value: the function gets a copy. Changes inside do not affect the original.
  • By reference: the function gets access to the original. Changes inside do affect it.

JavaScript: primitives (number, string, boolean) are passed by value. Objects and arrays pass a copy of the reference, so changes to their contents affect the original.

function addFee(amount) {
  amount += 1;
} // gets a COPY of the number
let price = 50;
addFee(price); // price is still 50

function addFeeToOrder(order) {
  order.total += 1;
} // gets a reference to the SAME object
const order = { total: 50 };
addFeeToOrder(order); // order.total is now 51

Strictly, JS is "pass by sharing": you can change the object's contents, but order = {} inside the function does not replace the caller's object. Why it matters: in React, change state by making a new object ({ ...order, total: 51 }), not by changing the old one. React compares references; if the reference is the same, it may not re-render.

Closure: a function that remembers the variables from where it was created.

function makeRateLimiter(max) {
  let count = 0; // remembered by the inner function
  return () => ++count <= max; // ++count: add 1 FIRST, then compare. True while under the limit
}
const allowPayment = makeRateLimiter(3);
allowPayment(); // true, true, true, then false

Event loop: JS runs one task at a time. Slow work (network, timers) goes to the browser/Node. When it finishes, its callback waits in a queue. The event loop runs it when the call stack is empty.

Call stack runs code → slow work goes to browser/Node → callback into a queue → event loop moves it to the stack when empty

Microtask vs macrotask: two waiting queues. Promise callbacks (microtasks) all run before the next timer or I/O callback (setTimeout, macrotasks).

setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("sync");
// sync → promise → timer

Promise: an object for a future value. States: pending, fulfilled, rejected. async/await: cleaner Promise code.

async function getPayment(id) {
  const res = await fetch(`/api/payments/${id}`); // waits, but does not block the thread
  return res.json();
}
  • Promise.all: run in parallel; wait for all; if one fails, the whole thing fails at once.
  • Promise.allSettled: wait for all; give back each result (success or failure). Good when some may fail.
  • Promise.race: give back the first one to finish. Example: a request vs a 5-second timeout.

Debounce: wait until the user stops typing before acting. Example: card-number lookup after 300 ms of no typing. Throttle: run at most once per time window. Example: a scroll handler once per 100 ms.

A10. Front end: React, Next.js and the web

How a page gets to the screen

Browser asks for URL → server sends HTML → browser builds the DOM → loads CSS (CSSOM)
      → runs JavaScript → layout → paint → page on screen

DOM: the tree of HTML elements in the browser. JavaScript changes the page by changing the DOM. CSSOM: the same kind of tree, but for the CSS styles. Render-blocking: CSS and some scripts stop the page from showing until they load. Fix: defer (load now, run after the HTML is read) or async (run as soon as loaded) on scripts.

React

React: a library to build UIs from components. You describe the UI for the current data; React updates the screen when the data changes. Virtual DOM + reconciliation: React builds a new copy of the UI in memory, compares it with the old copy (diffing), and updates only the changed parts of the real DOM. Example: one transaction status in a list of 100 changes → React updates 1 row.

TopicShort answer
Props vs stateProps: data passed in from the parent, read-only. State: data the component owns; when it changes, React re-renders (draws it again)
What causes a re-render?State change, props change, parent re-renders, context value changes
key in listsStable ID so React knows which item changed. Use transaction.id, not the index
Controlled vs uncontrolled inputControlled: React state holds the input value (easy to validate). Uncontrolled: the browser holds it; you read it when needed with a ref
useStateA value that can change
useEffectSide effects (work outside drawing the UI: fetch, timers) after render. Return a clean-up function that runs when the component leaves
useMemo / useCallback / React.memoRemember (cache) a calculated value / a function / a component's output, so React does not redo work when nothing changed. Use only for real performance problems
useRefKeeps a value that does not cause a re-render when it changes, or points to a real DOM element (e.g. to focus an input)
useContextRead shared data without passing props through many levels
Context vs Redux/ZustandContext: share data that changes rarely (theme, logged-in user). A state library: big, often-changing shared state, with less re-rendering
Server stateData that comes from an API. Libraries like React Query / SWR handle caching, loading spinners, errors and refetching for you
Custom hookYour own function starting with use that bundles hooks so many components can reuse them. Example: usePaymentStatus(id)
Error boundaryCatches render errors in children and shows a fallback, so the whole app does not crash
Class vs function componentsOld: classes with lifecycle methods (componentDidMount etc.). Now: functions + hooks (simpler, less code)
useEffect(() => {
  const id = setInterval(refreshPaymentStatus, 5000);
  return () => clearInterval(id); // clean-up: without it, timers pile up (memory leak)
}, []);

CSR, SSR, SSG, ISR

CSRSSRSSGISR
HTML builtIn the browserOn the server, each requestAt build timeAt build, then refreshed
First loadSlowFastFastestFastest
SEOWeakGoodGoodGood
FreshnessLiveLiveOld until rebuildNearly live
ExampleInternal admin dashboardAccount / statement pageHelp pagesProduct / pricing pages

CSR / SSR / SSG / ISR: Client-Side Rendering (browser builds the page) / Server-Side Rendering (server builds it per request) / Static Site Generation (built once when you build the app) / Incremental Static Regeneration (built once, then rebuilt in the background every N seconds). SEO = how well search engines (Google) can read and rank the page (see SEO below). Hydration: after SSR, React runs in the browser and attaches click handlers etc. to the server HTML, so the page becomes interactive. Hydration error: server HTML and first browser render differ. Example: Date.now() or window used during render.

Next.js

Next.js: a React framework with routing, server rendering, API routes, bundling and optimisation built in.

Why Next.js:

React Server Components (RSC): run only on the server, can read the database, send no JS to the browser. Add "use client" only where you need state, effects or events. Example: the statement list is a server component; the "Dispute transaction" button is a client component. Server actions: server functions called from a form, with no hand-written API route. Next.js middleware: code that runs before a page or API request is handled. Example: redirect users who are not logged in.

Trade-offs: more complex than plain React; needs a server for SSR; you must think about what runs where.

Compilation: front end vs back end vs C++

(For JIT vs AOT advantages, disadvantages and uses, see A16.)

Front end:  TS/TSX → transpile (tsc/Babel/SWC/esbuild) → bundle (webpack/Vite/Turbopack)
            → tree shake → minify → code split → small files over the network → browser JIT
Node:       TS → transpile → Node runs it (V8 JIT). Bundle size matters much less
C++:        source → compiler → machine code for one CPU (ahead of time) → linker → executable

Transpile: convert to another language at the same level (TS → JS). Types are removed. Bundler: joins many code files (modules) and libraries into a few files, so the browser makes fewer downloads. Tree shaking: removes code you import but never use. Minify: removes spaces and comments and shortens names, so files are smaller. Code splitting: each page loads only what it needs. Source map: maps minified code back to the original for debugging. Polyfill: adds a modern feature to an old browser. JIT: the engine compiles JS to machine code while it runs. AOT: compile before it runs (C++), predictable speed.

Front-end JSNode.jsC++
To machine codeAt run time (JIT)At run time (JIT)Before run time (AOT)
Build goalSmall download, browser supportCorrect JS for the Node versionFast, predictable code
Type checksBuild time onlyBuild time onlyCompile time

SEO

SEO: make pages easy for search engines to find, read and rank.

Core Web Vitals (Google's page-quality scores): LCP (Largest Contentful Paint): how fast the main content shows (< 2.5 s) · INP (Interaction to Next Paint): how fast the page reacts to a click (< 200 ms) · CLS (Cumulative Layout Shift): how much the page jumps while loading (< 0.1).

Real-time: WebSockets, SSE, polling

WebSocket: an open, two-way connection. Starts as HTTP, then upgrades. Example: live fraud alerts on an ops dashboard.

MethodHowUse for
PollingAsk every N secondsSlow-changing data
Long pollingBrowser asks; the server keeps the request open until there is new data, then the browser asks againOld fallback
SSE (Server-Sent Events)One-way stream, server → browser, over normal HTTPLive payment status, notifications
WebSocketTwo-way, always openChat, live trading, collaboration

CSS (basics)

Web performance

CDN: servers in many places that keep copies of your files; users get them from the nearest one.

Front-end security

Accessibility

Front-end testing

render(<PaymentForm />);
await userEvent.type(screen.getByLabelText("Amount"), "50");
await userEvent.click(screen.getByRole("button", { name: "Pay" }));
expect(await screen.findByText("Payment approved")).toBeInTheDocument();

Your link: "Jest, RTL and Cypress at BAE; components in Storybook."

30-second answers

"What is SSR and why Next.js?"

"SSR means the server builds the HTML for each request, so the user sees content fast and search engines can read it. Then React hydrates the page to make it interactive. Next.js lets me choose SSR, static or client rendering per page, and gives routing, code splitting, image optimisation and server code in one framework. The trade-off is more complexity and a server to run."

"Front-end vs back-end compilation?"

"On the front end, TypeScript and JSX are transpiled, bundled, tree-shaken, minified and split, because code goes over the network to many browsers. On Node, I transpile and run; bundle size matters much less. Both are JIT-compiled at run time. C++ compiles ahead of time to machine code for one CPU, which gives predictable performance."

"When would you use WebSockets?"

"When the server must push updates in real time, or both sides send often, like a live dashboard. For one-way updates SSE is simpler. I always use TLS, authenticate the connection, and handle reconnects and heartbeats."

A11. TypeScript

QuestionShort answer
Why TypeScript?Errors at build time, safe refactors, self-documenting. Types are gone at run time → validate input (Zod)
type vs interfaceBoth describe the shape of an object. interface can be extended and merged; type can also do unions (A | B). Pick one style and be consistent
any vs unknownany turns checks off; unknown forces a check. Prefer unknown
GenericsA placeholder type, filled in when used, so one function works for many types: function first<T>(items: T[]): T → first<Card>(cards) returns a Card
Union + narrowingA union = "one of these types" (string | number). Narrowing = a check that tells TS which one it is now (if (typeof x === "string"))
Utility typesBuilt-in helpers that make new types from old ones: Partial<T> (all fields optional), Pick<T, K> (only some fields), Omit<T, K> (all but some), Record<K, V> (object with keys K and values V)
Structural typingTS checks the shape (the fields), not the class name. Any object with the right fields fits
enum vs string unionenum Status { Paid, Failed } creates real JS code. A union "paid" | "failed" creates none. Many teams prefer unions
as const / readonlyas const: treat values as exact, read-only ("paid", not any string). readonly: a field that cannot change after creation
?. and ??Stop at null/undefined; default only for null/undefined (not 0 or "", unlike ||)
keyof / typeofkeyof Card = the list of Card's field names. typeof x = "the type of this variable"
ReturnType, Parameters, AwaitedGet a function's return type / its parameter types / the value inside a Promise, without writing them again
as (assertion)"Trust me", no run-time check. Use rarely
satisfiesChecks a value matches a type, but keeps TS's more exact guess of the type ("inferred" = worked out by TS)
strict modeTurns on the strong checks, e.g. strictNullChecks (you must handle null/undefined). Always on
tsconfig.jsonThe TS settings file: which JS version to output (target), module type, strictness, folder paths
.d.ts filesType-only files that describe a plain JS library, so TS can check its use (@types/express)
Mapped / conditional typesBuild a new type by looping over another type's fields: { [K in keyof T]?: T[K] } = "every field of T, made optional" (this is how Partial<T> works)
catch (err) typeerr is unknown (anything can be thrown) → check it, e.g. if (err instanceof Error), before use

Discriminated union: a union with a shared "tag" field; TS narrows the type by the tag.

type Payment =
  | { kind: "card"; last4: string }
  | { kind: "paypal"; email: string };
if (p.kind === "card") p.last4; // TS knows p is the card type here

Your link: "One template per notification type; a union like this makes sure each type is handled."

Type guard: a check that tells TS the type, e.g. typeof x === "string" or a function returning x is Card.

Typed Express handler (your back end):

app.post("/payments", (req: Request<{}, {}, PaymentBody>, res: Response) => {
  const body = PaymentSchema.parse(req.body); // run-time check: types vanish at run time
  res.status(201).json(body);
});

"Why TypeScript?" (30 seconds)

"It finds many errors at build time, not in production. Refactoring is safer because the compiler shows every place a change breaks. The types also document the code. But types do not exist at run time, so I still validate outside data, like API input, with a schema library."


A12. Testing, CI/CD, configuration and change management

Testing pyramid:  many unit (fast) → some integration → few end-to-end (slow)
TermMeaningYour link
Unit testTests one small piece (a function or component) alone, with its dependencies fakedJest, React Testing Library
Integration testTests several real parts together (API + database)—
E2E (end-to-end) testTests the whole app in a browser, like a real user clickingCypress
MockA fake version of something your code depends on, so the test is fast and controlledFake payment gateway that always says "approved"
TDD (Test-Driven Development)Write a failing test first (red) → write code until it passes (green) → clean up the code (refactor)—
Flaky testPasses and fails with no code changeYour CI/CD story (pipeline time, 10–20%)
CI / CDCI (Continuous Integration): every push is built and tested automatically. CD (Continuous Delivery/Deployment): code that passes is released automatically or with one clickYour pipelines at NG
Configuration managementRecord exactly which versions of code, tools and documents make up each release. Baseline = an approved, frozen snapshot; changes after it need approvalGit tags, releases
Change controlA formal route for every change: change request → impact analysis (what could it break?) → approval → implement → verifyControlled releases
Change Control Board (CCB)The group that approves changes—
DocumentationIn safety work, documents are the evidence; write them with the work, keep them in version control—

A13. Git

A14. Architecture, deployment and good practice

Monolith vs microservices

MonolithMicroservices
ShapeOne appMany small services, one job each
Start / test / deployEasyHarder: calls go over the network (can fail), more things to monitor, versions must stay compatible
ScaleCopy the whole appCopy only the busy service
TeamsShare one codebase and one releaseEach team can release its service alone

Good answer: "Start with a monolith; split only when there is a clear need, like different scaling or team ownership." Your link: "I designed several microservices at NG, including the notifications service."

Related terms:

API gateway: one front door for all services. It sends each request to the right service, and checks login and rate limits in one place. Service discovery: services find each other by name (payments-service), not by a fixed IP address that can change. Kubernetes does this with its own DNS. Event-driven: a service announces "PaymentSucceeded"; any other service that cares reacts. The sender does not need to know who listens (loose coupling). Saga: a multi-service transaction made of steps with undo steps. Example: reserve stock → charge card → if charge fails, release stock. Eventual consistency: data in different services agrees after a short delay, not instantly. Example: a payment shows in your banking app a few seconds after the shop says "paid".

Docker and Kubernetes

Docker container: the app plus everything it needs, packed to run the same everywhere. Stops "it works on my machine". Image vs container: the template vs a running copy (like class vs object). Kubernetes: runs and manages many containers: restarts failed ones, scales, rolls out new versions.

K8s termMeaning
PodThe smallest unit Kubernetes runs: one (or a few) containers together, with one IP address
DeploymentKeeps N copies of a pod running; rolling update = replace old pods with new ones a few at a time, so there is no downtime
ServiceA stable name and address for a group of pods (pods come and go; the Service name stays)
ConfigMap / SecretSettings given to pods from outside the code / the same for sensitive values (passwords, keys)
Liveness / readiness probeHealth checks Kubernetes calls: "Is it alive?" (if not, restart it) / "Is it ready for traffic?" (if not, send no requests yet)
Horizontal Pod AutoscalerAdds pods when load (e.g. CPU use) goes up, removes them when it goes down

Your link: "Kubernetes and Linux are on my CV; I deploy and debug services there."

Serverless

Serverless (AWS Lambda): you write functions; the cloud runs and scales them; you pay per use. Good: no servers to look after, scales by itself. Bad: cold starts (first call after a quiet time is slow, because a new copy must start), time limits per call, harder to test on your laptop. Your link: "First-of-type serverless solution at BAE: Python, Lambda, S3, DynamoDB, CDK, CloudFormation." Infrastructure as code: define cloud resources (databases, queues, functions) in code files (CDK, CloudFormation, Terraform), reviewed in Git, instead of clicking in a web console. Repeatable and reviewable.

Logging, monitoring, observability

Clean code and good practice

PracticeOne line
KISSKeep it simple, stupid: the simplest solution that works is usually best
DRY vs DAMPDRY (Don't Repeat Yourself): put shared logic in one place. DAMP (Descriptive And Meaningful Phrases): readable code first; tests may repeat a little so each test is clear on its own
YAGNIYou Aren't Gonna Need It: do not build features "just in case"
Defensive programmingCheck inputs and assumptions; never trust outside data
Code reviewA second person checks every change; review for correctness, tests, readability
Static analysis / lintingTools that check code for bugs and style without running it: ESLint (JS/TS), SonarQube, clang-tidy (C++)
Dependency scanning / SBOMCheck your libraries for known security holes (npm audit, Dependabot). SBOM (Software Bill of Materials) = a list of every component and version in your software
Technical debtQuick shortcuts that make future changes slower or riskier (like interest on a loan); track them as tickets. Your link: legacy modernisation
Post-incident reviewAfter an outage: find the root cause (the real first reason), no blame, add a test or check so it cannot happen again
Feature flagsA setting that turns a feature on/off without a new release; switch it on for a few users first, and switch it off fast if it breaks

AI-assisted development (safely)

A15. Live coding round

1. Repeat the problem in your own words
2. Ask about edge cases (empty input? negative amounts? duplicates?)
3. Explain a simple solution first
4. Write code while talking
5. Test with an example
6. Talk about Big O and how to improve

Common topics: arrays and strings (reverse, find duplicates, two-sum = find two numbers that add up to a target), hash maps (count items, fast lookup), recursion (a function that calls itself on a smaller part), sorting, Big O.

Example: "Find duplicate transactions": use a Set of seen IDs → O(n), not a loop in a loop (O(n²)).

A16. Compilation: AOT vs JIT (and interpreted)

First, the basic words

CPU: the chip that runs the program. Machine code: the only language a CPU understands: numbers that mean "add", "copy", "jump". Each CPU family has its own (Intel/AMD x86, phone and board chips ARM). Compile: translate your code (C++, Java, JS) into machine code. Runtime: a program that must run next to your code to make it work. Example: Node.js is the runtime for your JavaScript.

So every language must become machine code at some point. The only question is when.

The two answers: before, or during

AOT (Ahead-Of-Time): compile before the program runs. You ship the finished machine code. Example: C and C++. You build an .exe (or firmware file) once, then it runs directly on the CPU.

JIT (Just-In-Time): compile while the program runs. Example: your Node.js code. V8 (the engine inside Node and Chrome) starts running your JS at once, then compiles the busy parts into machine code as it goes.

Interpreted: a program reads your code and does each step, with almost no compiling. Simple, but slower. Example: classic Python.

AOT:   your code → [compile] → machine code → run           (compile BEFORE)
JIT:   your code → run → [compile the busy parts] → keep running faster   (compile DURING)

Words you will hear in follow-up questions

Hot code: the parts of the program that run very often. Example: the function that handles every API request. A JIT compiles these first, because they give the biggest speed gain.

Warm-up: the slow start of a JIT program, before the hot code is compiled. Example: the first few requests to a new Node server are a bit slower.

Cold start: warm-up in serverless (AWS Lambda). A new function copy starts from nothing, so the first request is slow. Your link: AWS Lambda at BAE.

Bytecode: a middle step between your code and machine code. It is the same on every CPU, so one file runs everywhere. Example: Java .class files.

Virtual machine (VM) in this context: the runtime that runs bytecode and does the JIT. Example: the JVM for Java. (Not the same as a cloud VM.)

Garbage collection (GC): the runtime finds memory you no longer use and frees it for you. It can pause your program for a short time, at moments you do not control.

Inlining: copy a small function's code into the place that calls it, so the program skips the "jump to the function" step. A JIT does this for hot code.

Predictable timing: the same work always takes about the same time. Important in real-time systems, where a late answer is a wrong answer.

Footprint: how much memory and storage the program needs.

Certify: prove to a regulator that software is safe enough (aircraft, air traffic, medical). Easier when the exact machine code is fixed before it runs.

Cross-compile: build on your PC for a different CPU. Example: compile on a laptop (x86) for an ARM board on a vehicle.

PGO (Profile-Guided Optimisation): an AOT trick to get some JIT benefits.

1. Build the program normally
2. Run it with typical work and record which parts are hot (the "profile")
3. Build again, telling the compiler which parts to optimise most

Example: a C++ server built with PGO can be a bit faster, because the compiler knows the hot paths.

Advantages and disadvantages

AOT (C, C++, Rust, Go)JIT (Java, C#, JavaScript/Node)
Start-upFast: machine code is readySlow at first (warm-up)
Speed after start-upFastFast, sometimes faster (it optimises hot code using real data)
TimingPredictableLess predictable (compile work + GC pauses)
MemorySmallBigger (runtime + compiler in memory)
Runs on other CPUs?Build again for each CPUYes, the same code runs anywhere the runtime exists
Build timeLongerShort: edit and run

Where each one is used

JobChooseSimple reason
Microcontroller, vehicle control, flight systemsAOTMust be on time, every time; little memory; must be certified
Web APIs and back ends (your Node services)JITServers run for days, so warm-up does not matter; fast to develop
JavaScript in the browserJITCode arrives as text and must run on any device
Small scripts, data analysisInterpreted (Python)Fast to write; speed is less important

Some systems mix both (Android; Java and .NET can also compile AOT). It is a trade-off, not "one is better".

30-second answer: "Why choose JIT or AOT?"

"AOT, like C and C++, compiles to machine code before the program runs. It starts fast, uses little memory and has predictable timing, so it suits embedded and real-time systems. The cost is a separate build for each CPU type. JIT, like Java or Node's V8, compiles while the program runs. It runs the same code on any machine and can optimise the busy parts using real data, so it suits long-running servers. The costs are a slow start, more memory and less predictable timing. For a vehicle controller I would choose AOT; for a web API, JIT is fine."

If they dig deeper

They askYou say
"Why is JIT timing unpredictable?""The runtime compiles code and frees memory (garbage collection) at moments you do not control, so some requests take longer."
"How can JIT be faster than AOT?""It sees how the program really runs, so it can optimise the hot code for real data, for example by inlining a function called millions of times."
"Can AOT get that benefit?""Partly, with PGO: run the program once, record the hot parts, then compile again with that information."
"What is a cold start?""In serverless, a new copy of the function starts from nothing, so the first request is slow. AOT builds start faster."
"Is JavaScript compiled or interpreted?""Both. V8 starts by interpreting it, then JIT-compiles the hot parts to machine code."

Your link: "My Node services run on V8, which uses JIT. That suits long-running APIs. For real-time or embedded work, I would choose AOT C or C++ for the predictable timing."


PART B: HARDWARE AND LOW LEVEL

Examples here use devices: a card terminal (payments hardware), an aircraft / vehicle sensor, a valve actuator (your Flowserve work).

B1. Memory

Stack: memory for local variables inside a function. Fast, automatic, small. Freed by itself when the function returns. Heap: a big pool of memory you ask for while the program runs (malloc/new). In C/C++ you must give it back yourself (free/delete). Garbage collection (GC): the runtime frees unused heap memory for you (JS, Java, Python). Easy, but GC pauses are not predictable → avoided in hard real-time code.

BugMeaning
Memory leakMemory never freed → program grows until it crashes
Dangling pointerA pointer to memory that was already freed; using it reads garbage or crashes
Buffer overflowWrites past the end of an array (e.g. 20 chars into a 10-char buffer) → overwrites other data; attackers use this to run their own code
NULL pointer useUsing a pointer that points to nothing (address 0) → crash
FragmentationFree memory is split into many small gaps, so a big request fails even if the total free memory is enough. This is why embedded code avoids the heap after start-up

B2. C and C++ core

Pointer: a variable that holds a memory address.

int x = 5; int *p = &x; *p = 10;  // x is now 10

Reference (C++): another name for a variable. Cannot be null, cannot be re-pointed.

Pass by value vs pointer vs reference (C++):

void byValue(int amount)       { amount += 1; } // gets a COPY → original unchanged
void byPointer(int* amount)    { *amount += 1; } // gets the ADDRESS → original changed
void byReference(int& amount)  { amount += 1; } // gets the ORIGINAL → original changed
void readOnly(const Order& o)  { /* read only */ } // no copy, and cannot change it

int price = 50;
byValue(price);      // price = 50
byPointer(&price);   // price = 51
byReference(price);  // price = 52
KeywordMeaning
constThe value cannot be changed through this name
static (local variable)Keeps its value between calls (not reset each time)
static (global variable / function)Visible only inside this .c/.cpp file
volatile"This can change outside this code, so always read it from memory" (stops the compiler from caching it). For hardware registers and ISR flags. Not thread-safe
inlineA hint to copy the function's code into the place it is called, to save the cost of a call
extern"This variable exists, but it is created in another file"

RAII (Resource Acquisition Is Initialisation): an object gets a resource (memory, a file, a lock) when it is created and frees it when it is destroyed, so you cannot forget. Example: std::lock_guard unlocks a mutex at the end of the { } block, even if an exception is thrown. Smart pointers: pointers that free memory by themselves. std::unique_ptr (one owner; freed when the owner goes), std::shared_ptr (many owners; it counts them and frees when the count is 0), std::weak_ptr (can look at a shared object but does not keep it alive). Avoid raw new/delete. Move semantics: hand over a big object's insides (e.g. its memory buffer) to a new owner instead of copying them (std::move). Much faster for big objects. Templates: write code once for any type; the compiler makes a version per type (std::vector<int>, std::vector<Card>). Compile-time polymorphism. Rule of 3/5/0: if your class needs a custom destructor, copy or move function, it probably needs all of them (3 or 5). Best: need none, because your members already clean up after themselves (rule of 0).

C vs C++: C is procedural (just functions and data, no classes), small, common on bare metal. C++ adds classes, RAII, templates, and the STL (Standard Template Library: ready-made containers like vector, map, and algorithms like sort).

Compile pipeline:

source (.c/.cpp) → preprocessor (#include, #define) → compiler (.o) → linker → executable / firmware image

Endianness: the order of bytes when a number is stored. 0x12345678: big-endian stores 12 34 56 78 (biggest byte first), little-endian stores 78 56 34 12 (smallest first). Matters when data goes over a network or between different processors: both sides must agree.

Bit manipulation:

reg |=  (1u << 3);   // set bit 3
reg &= ~(1u << 3);   // clear bit 3
reg ^=  (1u << 3);   // toggle bit 3
if (reg & (1u << 3)) // test bit 3

B3. Embedded basics

Embedded system: a computer inside a bigger device, made for one job. Example: a card terminal, a flight sensor, a valve controller. Bare metal: no operating system; your code runs straight on the chip, usually main() with an endless loop. RTOS (Real-Time Operating System): a small OS that guarantees tasks run on time (FreeRTOS, Zephyr, VxWorks). Embedded Linux: full Linux on a board; Yocto and Buildroot are tools that build a custom small Linux image.

Bare metalRTOSEmbedded Linux
SizeSmallestSmallBig
TimingFully predictableReal-time (deadlines guaranteed)Not hard real-time by default
FeaturesFewTasks (like threads), queues, mutexesFull OS, drivers, networking

Interrupt / ISR (Interrupt Service Routine): hardware pauses the normal code and runs a short function at once. Rules: keep it short, no waiting (blocking), no malloc (heap), no printf (slow). Just set a flag or push data to a queue; do the real work in the main loop. Example: a card is inserted → interrupt → ISR sets cardInserted = true → main loop reads the card. Register: a tiny memory slot inside a hardware part that controls it or shows its state (e.g. "bit 0 = LED on"). Memory-mapped I/O: hardware registers have normal memory addresses, so you read/write them like variables, using volatile pointers. Watchdog: a hardware timer that resets the system if the software stops "kicking" (resetting) it in time, e.g. because it froze. DMA (Direct Memory Access): hardware copies data into memory without using the CPU. Example: sensor samples go straight into a buffer while the CPU does other work. Hard vs soft real-time: hard = a missed deadline is a failure (flight control); soft = a missed deadline is only worse (a video frame drops). Polling vs interrupts: polling = the code keeps checking "is it ready yet?"; interrupts = the hardware tells you when it is ready. Interrupts save CPU and react faster.

InterfaceOne line
GPIO (General-Purpose Input/Output)One pin you set high/low (e.g. an LED) or read (e.g. a button)
UARTSimple serial link, one bit after another, 2 wires (TX send, RX receive). Used for debug consoles. RS-232/RS-485 are UART-style links over longer cables
SPIFast, 4 wires; one controller talks to many devices, choosing one with a select wire (flash memory, displays)
I2C2 wires shared by many devices; each device has an address. Slower (sensors)
CANA robust shared bus (many devices on one cable) for vehicles and industry; handles electrical noise well
EthernetNetworking

FPGA: a chip you program to become custom digital hardware. Very fast parallel processing. Common in defence and signal processing. Microcontroller vs microprocessor: CPU + memory + peripherals (timers, UART, GPIO) all on one chip, for small devices vs a CPU only, with memory outside it, powerful enough to run Linux. Firmware: software stored on the device that controls the hardware. Bootloader: small program that starts first and loads the main firmware; also used for firmware updates.

Your link: "At Flowserve I worked with pneumatic and hydraulic actuators and tested valves against operating limits. In embedded terms, the controller reads sensors and drives an actuator inside safe limits, with a watchdog and fail-safe state."

B4. Threads at low level

Mutex: a lock; only one thread can hold it at a time. Others wait. Semaphore: a counter for a limited resource (e.g. 3 free buffers), or a signal from one task to another ("data ready"). Atomic: an operation that completes in one step, so no other thread sees it half-done (std::atomic<int>, C11 stdatomic.h). Condition variable: lets a thread sleep (use no CPU) until another thread signals "something changed". Priority inversion: a low-priority task holds a lock that a high-priority task needs; a medium task keeps running and blocks both. Fix: priority inheritance (the low task briefly gets high priority to finish fast). Famous case: Mars Pathfinder, 1997. Producer–consumer: producer → thread-safe queue (mutex + condition variable) → consumer. Example: sensor ISR produces samples, a task consumes them.

Debugging threads: reproduce under heavy load → logs with timestamps and thread IDs → ask "what data is shared, and what protects it?" → tools: ThreadSanitizer / Helgrind (find races), GDB (info threads, thread apply all bt = show where every thread is) → fix → stress test (run it hard, many times).

B5. Debugging tools

ToolUse
GDBDebugger: stop at a line (breakpoint), step line by line, look at variables and memory
JTAG / SWDA hardware connection to the chip's pins, so a debugger can stop and inspect code running on the real board
Valgrind / AddressSanitizerFind memory leaks and bad memory use (overflows, use after free)
ThreadSanitizerRace conditions
Logic analyser / oscilloscopeShow the real electrical signals on wires: on/off patterns (logic analyser) or voltage over time (oscilloscope)
straceShows every request a Linux program makes to the OS (open file, read, connect), so you see where it fails or waits

B6. Linux

CommandWhat it does
ps / topList running processes / live view of CPU and memory use
grepSearch text in files or output
tail -fFollow a log file live as new lines are added
journalctl / systemctlRead system service logs / start, stop, check a service
chmodChange file permissions
ssh / scpLog in to another machine / copy files to it
dfShow free disk space
ssShow network connections and open ports
dmesgKernel messages (hardware and driver problems)
make / gccBuild a project / compile C code

Signal: OS message to a process. SIGINT (Ctrl+C), SIGTERM (please stop), SIGKILL (stop now), SIGSEGV (bad memory access).

Live issue on Linux: is the service running (systemctl status) → what do the logs say (journalctl, tail -f) → out of CPU, memory or disk (top, df) → network OK (ss) → reproduce in test → fix → release under change control.

Your link: "I use Linux daily: Bash scripts, containers and Kubernetes."

B7. Safety-critical and embedded practice

TermOne line
Safety-criticalA failure can hurt people (flight control, braking)
MISRA C / C++A set of safe-coding rules for C/C++ in cars, aircraft, defence (e.g. no heap after start-up, no exceptions)
Static analysisCheck code without running it (cppcheck, clang-tidy, MISRA checkers)
HAL (Hardware Abstraction Layer)Thin layer of functions that hide the hardware (led_on() instead of writing a register), so you can swap in a fake in tests
Unit tests on hostTest the logic on your PC (the "host"), not the board, with a fake HAL (Unity/Ceedling for C, GoogleTest for C++)
HIL (Hardware-in-the-Loop) testingRun the real software on real or simulated hardware, as part of the CI pipeline
Cross-compileBuild on your PC for another CPU (ARM)
V-modelA safety-friendly process: each design stage on the left (requirements → system design → detailed design → code) has a matching test stage on the right (acceptance ← system ← integration ← unit test)
Requirements traceabilityEach requirement is linked to its design, code and tests, so you can prove every requirement was built and tested
RedundancyA backup that takes over if the main part fails; it must not fail in the same way (e.g. same bug)
Fail-safe vs fail-operationalOn a fault, go to a safe state (stop, alarm) vs keep working (e.g. reject only the bad input)
Assurance levelsHow much evidence software needs, based on how bad a failure could be (DO-178C for aircraft, ED-153 for ATM ground systems)

Embedded CI/CD:

Push → cross-compile → static analysis → unit tests on host → flash to test board → HIL tests → signed release

("Flash" = write the firmware onto the board's memory. "Signed release" = the release file has a digital signature, so the device can check it is genuine.)

Your link: "My CI/CD work transfers directly: same pipeline idea, plus cross-compiling and hardware-in-the-loop stages."


PART C: LAST-MINUTE QUICK-FIRE LIST

Say each answer in one breath:

  1. Class vs object: template vs one instance (Card class vs your card). 1b. Method vs function: belongs to a class/object (card.charge()) vs stands alone (addVat()). 1c. Pass by value vs reference: a copy (original unchanged) vs the original (changes affect it). JS: numbers/strings by value, objects by reference.
  2. Encapsulation: hide data, change it only through methods (card number private, only last 4 shown; balance only via charge()).
  3. Abstraction: show what, hide how (call pay(); the steps to pay are hidden inside).
  4. Inheritance: child inherits the parent's properties and methods, can add extra ones and override them, "is-a" (CreditCard is a Card; shared checks written once).
  5. Polymorphism: same call, different behaviour (one pay() → card or PayPal). 5b. public / private / protected: anyone / only this class / this class + subclasses (pay() / balance / checkExpiry()). Start private.
  6. Overloading vs overriding: same name, different parameters, same class, compile time vs child replaces parent method, run time.
  7. Operator overloading: custom meaning for +, == (Money + Money). C++ yes; Java/JS/TS no.
  8. Interface vs abstract class: list of methods with no code, a class can implement many vs a base class with some shared code, a class can extend only one.
  9. Composition vs inheritance: has-a vs is-a; prefer composition.
  10. SOLID: one job per class, add new classes instead of editing old ones, a child works wherever the parent works, small interfaces, depend on interfaces not specific classes.
  11. Idempotent: retry = same result (idempotency key → no double charge).
  12. ACID: database transactions are all-or-nothing, follow the rules, do not mix, and survive a crash (e.g. a transfer).
  13. Race condition: two threads, shared data, timing decides (double spend).
  14. Deadlock: wait for each other; same lock order.
  15. Concurrency vs parallelism: taking turns vs at the same time.
  16. JS threads: one thread + event loop; workers for CPU work; async ≠ parallel.
  17. Exceptions: unexpected and unrecoverable → throw; expected (card declined) → return a result.
  18. TLS: encrypts traffic, detects changes, proves the server's identity; uses a certificate (server ID) and a shared session key.
  19. Stack vs heap: automatic local vs manual (or GC).
  20. volatile: always read memory; for registers/ISR flags; not thread-safe.
  21. RAII: a resource is freed automatically when its object is destroyed (lock_guard unlocks at end of block).
  22. ISR rules: short, no blocking, set a flag.
  23. Bare metal / RTOS / Linux: no OS / real-time OS / full OS.
  24. Fail-safe: safe state on fault.
  25. JIT vs AOT: compile while running (portable, smart optimisation, warm-up, less predictable) vs before running (fast start, predictable, small; one build per CPU). Real-time/embedded → AOT.

If you go blank: "Good question, let me think for a moment." Then use the formula: definition → example → why → trade-off.