Episode 9 — System Design / 9.1 — LLD Foundations
9.1.e — LLD Interview Approach
In one sentence: A structured approach to LLD interviews follows six steps — clarify requirements, identify entities, define classes, establish relationships, define methods, and handle edge cases — and the ability to execute this framework calmly under time pressure is what separates strong candidates from average ones.
Table of Contents
- 1. The 6-Step Framework
- 2. Step 1 — Clarify Requirements
- 3. Step 2 — Identify Entities
- 4. Step 3 — Define Classes
- 5. Step 4 — Establish Relationships
- 6. Step 5 — Define Methods
- 7. Step 6 — Handle Edge Cases
- 8. Time Management in a 45-Minute Interview
- 9. Common Mistakes
- 10. Example Walkthrough — Parking Lot System
- 11. Key Takeaways
1. The 6-Step Framework
Every LLD interview question can be tackled with this framework:
┌─────────────────────────────────────────────────────────────────────┐
│ LLD INTERVIEW FRAMEWORK │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 1: CLARIFY REQUIREMENTS (5 min)│ │
│ │ Ask questions. Define scope. │ │
│ └──────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 2: IDENTIFY ENTITIES (5 min) │ │
│ │ Find the nouns. List core objects. │ │
│ └──────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 3: DEFINE CLASSES (10 min) │ │
│ │ Attributes, types, responsibilities. │ │
│ └──────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 4: ESTABLISH RELATIONSHIPS │ │
│ │ (8 min) Association, composition, │ │
│ │ inheritance, interface. │ │
│ └──────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 5: DEFINE METHODS (10 min) │ │
│ │ Core operations. Signatures. Logic. │ │
│ └──────────────┬───────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ STEP 6: HANDLE EDGE CASES (7 min) │ │
│ │ Error handling. Concurrency. │ │
│ │ Design patterns. Extensions. │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
2. Step 1 — Clarify Requirements
Never jump straight into design. The first 3–5 minutes should be spent asking clarifying questions to define the scope.
What to Clarify
| Category | Sample Questions |
|---|---|
| Core features | "What are the must-have features? Should I focus on X or Y?" |
| Users/Actors | "Who interacts with this system? Admin and regular users both?" |
| Scale | "How many concurrent users? Thousands or millions?" (less relevant for LLD, but shows maturity) |
| Constraints | "Is there a max limit on X? Do we need to handle multi-threading?" |
| Out of scope | "Should I handle payments, or is that a separate service?" |
| Edge cases early | "What happens if X fails? Should we support cancellation?" |
Example: "Design a Parking Lot System"
Candidate: "Before I start, let me ask a few questions to clarify scope."
Q1: "What types of vehicles should we support? Just cars, or also
motorcycles and trucks?"
A: "Cars, motorcycles, and trucks."
Q2: "Is there a single entrance/exit, or multiple?"
A: "Multiple entrances and exits."
Q3: "Should the system handle payment?"
A: "Yes — basic payment when exiting."
Q4: "Do we need different pricing for different vehicle types?"
A: "Yes — motorcycles cheaper than cars cheaper than trucks."
Q5: "Should I handle a display board that shows available spots?"
A: "Yes, that would be nice."
Q6: "Should I worry about concurrency — multiple cars entering
at the same time?"
A: "Let's handle it at a basic level."
Why this matters: These questions narrow the problem from "vague parking lot" to a specific set of classes and features. Interviewers explicitly look for this.
3. Step 2 — Identify Entities
Scan the requirements and extract the nouns — these become your candidate classes.
Technique: The Noun Extraction Method
- Write down the requirement sentences.
- Underline every noun.
- Filter out duplicates and irrelevant nouns.
- Group related nouns.
Requirement:
"A parking lot has multiple floors. Each floor has parking spots of
different sizes (small, medium, large). Vehicles (cars, motorcycles,
trucks) enter through entrance panels and exit through exit panels.
The system issues a parking ticket when a vehicle enters and
calculates payment when the vehicle exits."
Extracted nouns:
parking lot, floor, parking spot, size, vehicle, car,
motorcycle, truck, entrance panel, exit panel,
parking ticket, payment
Filtered entities (classes):
ParkingLot, ParkingFloor, ParkingSpot, Vehicle (Car, Motorcycle, Truck),
EntrancePanel, ExitPanel, ParkingTicket, Payment
Tips for Entity Identification
| Tip | Explanation |
|---|---|
| Nouns = potential classes | ParkingLot, Vehicle, Ticket are all nouns |
| Verbs = potential methods | "enters", "exits", "calculates" become methods on classes |
| Adjectives = potential enums/types | "small, medium, large" becomes a SpotSize enum |
| "Types of X" = inheritance | "cars, motorcycles, trucks" are types of Vehicle |
| Not everything is a class | "size" might be an enum, not a class |
4. Step 3 — Define Classes
For each entity, define its attributes (data it holds) and its responsibility (what it is supposed to do).
Template for Each Class
Class: [Name]
Responsibility: [One sentence — what this class is for]
Attributes:
- field1: Type
- field2: Type
Key methods:
- method1(): ReturnType
- method2(param): ReturnType
Example Classes for Parking Lot
enum VehicleType {
MOTORCYCLE = "MOTORCYCLE",
CAR = "CAR",
TRUCK = "TRUCK",
}
enum SpotSize {
SMALL = "SMALL", // for motorcycles
MEDIUM = "MEDIUM", // for cars
LARGE = "LARGE", // for trucks
}
enum TicketStatus {
ACTIVE = "ACTIVE",
PAID = "PAID",
}
// Vehicle hierarchy (inheritance)
abstract class Vehicle {
private licensePlate: string;
private type: VehicleType;
constructor(licensePlate: string, type: VehicleType) {
this.licensePlate = licensePlate;
this.type = type;
}
getLicensePlate(): string { return this.licensePlate; }
getType(): VehicleType { return this.type; }
}
class Car extends Vehicle {
constructor(licensePlate: string) {
super(licensePlate, VehicleType.CAR);
}
}
class Motorcycle extends Vehicle {
constructor(licensePlate: string) {
super(licensePlate, VehicleType.MOTORCYCLE);
}
}
class Truck extends Vehicle {
constructor(licensePlate: string) {
super(licensePlate, VehicleType.TRUCK);
}
}
Responsibility Check
For each class, verify: "Can I describe this class's responsibility in one sentence?"
| Class | Responsibility |
|---|---|
| ParkingLot | Manages floors, entrances, exits, and coordinates the overall system |
| ParkingFloor | Manages a collection of parking spots on a single floor |
| ParkingSpot | Represents a single space that can hold one vehicle |
| Vehicle | Represents a vehicle with a license plate and type |
| ParkingTicket | Records the entry of a vehicle — tracks time and payment |
| EntrancePanel | Handles vehicle arrival and ticket issuance |
| ExitPanel | Handles vehicle departure and payment processing |
If a class has two unrelated responsibilities, split it.
5. Step 4 — Establish Relationships
Connect your classes using the correct relationship types from 9.1.c.
Relationship Decision for Parking Lot
| Relationship | Type | Reasoning |
|---|---|---|
| ParkingLot → ParkingFloor | Composition (◆) | Floors cannot exist without the lot |
| ParkingFloor → ParkingSpot | Composition (◆) | Spots cannot exist without the floor |
| ParkingSpot → Vehicle | Association | Spot references a vehicle; vehicle exists independently |
| ParkingLot → EntrancePanel | Composition (◆) | Panels are part of the lot |
| ParkingLot → ExitPanel | Composition (◆) | Panels are part of the lot |
| ParkingTicket → Vehicle | Association | Ticket references the vehicle |
| Car → Vehicle | Inheritance (▷) | Car IS-A Vehicle |
Draw the Diagram
┌────────────────────┐
│ ParkingLot │
├────────────────────┤
│ - name: str │
│ - address: str │
├────────────────────┤
│ + getAvailableSpots│
│ + isFull(): bool │
└───┬────┬───┬───┬───┘
◆ 1..* │ │ │ │ ◆ 1..*
┌────────────────┘ │ │ └──────────────┐
│ ◆ 1..* ◆ 1..* │
┌─────────▼────┐ ┌─────▼─────┐ ┌──▼───────────┐ ┌──▼───────────┐
│ ParkingFloor │ │ Entrance │ │ ExitPanel │ │<<interface>> │
├──────────────┤ │ Panel │ ├──────────────┤ │ PaymentStrategy
│ - floorNum │ ├───────────┤ │ + scanTicket │ ├──────────────┤
│ - spots[] │ │ +getTicket│ │ + processExit│ │ +calculate() │
├──────────────┤ └───────────┘ └──────────────┘ └──────▲───────┘
│ +findAvail() │ ┆ impl
│ +parkVehicle │ ┌─────┴──────┐
└──────┬───────┘ │ │
│ ◆ 1..* ┌────┴───┐ ┌────┴────┐
┌──────▼───────┐ ┌──────────────┐ │Hourly │ │FlatRate │
│ ParkingSpot │ │ ParkingTicket│ │Strategy│ │Strategy │
├──────────────┤ ├──────────────┤ └────────┘ └─────────┘
│ - spotId │ │ - ticketId │
│ - size: Size │ │ - entryTime │
│ - isOccupied │ │ - exitTime │
│ - vehicle? │ │ - vehicle │
├──────────────┤ │ - status │
│ +park() │ ├──────────────┤
│ +unpark() │ │ +markPaid() │
│ +canFit(v) │ │ +getDuration │
└──────────────┘ └──────────────┘
6. Step 5 — Define Methods
Now implement the core logic. Focus on the most important operations — do not write every getter and setter.
Core Methods for Parking Lot
class ParkingSpot {
private spotId: string;
private size: SpotSize;
private isOccupied: boolean;
private vehicle: Vehicle | null;
constructor(spotId: string, size: SpotSize) {
this.spotId = spotId;
this.size = size;
this.isOccupied = false;
this.vehicle = null;
}
canFitVehicle(vehicle: Vehicle): boolean {
if (this.isOccupied) return false;
// Size matching: motorcycle → SMALL+, car → MEDIUM+, truck → LARGE only
const sizeOrder = { SMALL: 1, MEDIUM: 2, LARGE: 3 };
const vehicleSizeNeeded: Record<VehicleType, SpotSize> = {
MOTORCYCLE: SpotSize.SMALL,
CAR: SpotSize.MEDIUM,
TRUCK: SpotSize.LARGE,
};
const neededSize = sizeOrder[vehicleSizeNeeded[vehicle.getType()]];
const spotSize = sizeOrder[this.size];
return spotSize >= neededSize;
}
park(vehicle: Vehicle): void {
if (this.isOccupied) throw new Error("Spot already occupied");
if (!this.canFitVehicle(vehicle)) throw new Error("Vehicle too large for this spot");
this.vehicle = vehicle;
this.isOccupied = true;
}
unpark(): Vehicle {
if (!this.isOccupied || !this.vehicle) throw new Error("Spot is empty");
const vehicle = this.vehicle;
this.vehicle = null;
this.isOccupied = false;
return vehicle;
}
getSpotId(): string { return this.spotId; }
getSize(): SpotSize { return this.size; }
getIsOccupied(): boolean { return this.isOccupied; }
}
class ParkingFloor {
private floorNumber: number;
private spots: ParkingSpot[];
constructor(floorNumber: number, spots: ParkingSpot[]) {
this.floorNumber = floorNumber;
this.spots = spots;
}
findAvailableSpot(vehicle: Vehicle): ParkingSpot | null {
return this.spots.find(spot => spot.canFitVehicle(vehicle)) ?? null;
}
getAvailableSpotCount(): number {
return this.spots.filter(spot => !spot.getIsOccupied()).length;
}
getFloorNumber(): number { return this.floorNumber; }
}
class ParkingTicket {
private ticketId: string;
private vehicle: Vehicle;
private spot: ParkingSpot;
private entryTime: Date;
private exitTime: Date | null;
private status: TicketStatus;
constructor(vehicle: Vehicle, spot: ParkingSpot) {
this.ticketId = `TKT-${Date.now()}-${Math.random().toString(36).substring(7)}`;
this.vehicle = vehicle;
this.spot = spot;
this.entryTime = new Date();
this.exitTime = null;
this.status = TicketStatus.ACTIVE;
}
markExit(): void {
this.exitTime = new Date();
}
markPaid(): void {
this.status = TicketStatus.PAID;
}
getDurationHours(): number {
const end = this.exitTime ?? new Date();
return (end.getTime() - this.entryTime.getTime()) / (1000 * 60 * 60);
}
getVehicle(): Vehicle { return this.vehicle; }
getSpot(): ParkingSpot { return this.spot; }
getTicketId(): string { return this.ticketId; }
getStatus(): TicketStatus { return this.status; }
}
class ParkingLot {
private name: string;
private floors: ParkingFloor[];
private activeTickets: Map<string, ParkingTicket>; // licensePlate → ticket
constructor(name: string, floors: ParkingFloor[]) {
this.name = name;
this.floors = floors;
this.activeTickets = new Map();
}
// Core operation: vehicle enters the lot
enterVehicle(vehicle: Vehicle): ParkingTicket {
// Find an available spot across all floors
for (const floor of this.floors) {
const spot = floor.findAvailableSpot(vehicle);
if (spot) {
spot.park(vehicle);
const ticket = new ParkingTicket(vehicle, spot);
this.activeTickets.set(vehicle.getLicensePlate(), ticket);
return ticket;
}
}
throw new Error("Parking lot is full — no suitable spot available");
}
// Core operation: vehicle exits the lot
exitVehicle(licensePlate: string): ParkingTicket {
const ticket = this.activeTickets.get(licensePlate);
if (!ticket) throw new Error("No active ticket found for this vehicle");
ticket.markExit();
ticket.getSpot().unpark();
this.activeTickets.delete(licensePlate);
return ticket;
}
isFull(): boolean {
return this.floors.every(floor => floor.getAvailableSpotCount() === 0);
}
getAvailableSpotCount(): number {
return this.floors.reduce(
(total, floor) => total + floor.getAvailableSpotCount(), 0
);
}
}
7. Step 6 — Handle Edge Cases
This step separates good candidates from great ones. Proactively identify potential issues.
Edge Cases for Parking Lot
| Edge Case | How to Handle |
|---|---|
| Lot is full | enterVehicle() throws an error. Display board shows "FULL". |
| Vehicle already parked | Check activeTickets before allowing entry |
| Invalid ticket / lost ticket | Charge maximum rate; require manual verification |
| Concurrent entry | Two cars try to park in the same spot simultaneously — use locking |
| Vehicle type mismatch | Truck tries to park in a small spot — canFitVehicle() rejects it |
| Payment failure | Gate does not open; retry payment or escalate to attendant |
| Power outage | Persist ticket data to database, not just in-memory |
| Midnight boundary | Duration calculation handles date rollover correctly |
Mentioning Design Patterns
At this stage, mention patterns you would apply:
┌──────────────────────────────────────────────────────────────────┐
│ DESIGN PATTERNS TO MENTION │
│ │
│ Strategy Pattern → Different payment calculation strategies │
│ (hourly, flat rate, weekend rate) │
│ │
│ Singleton Pattern → ParkingLot instance (only one lot) │
│ │
│ Factory Pattern → Creating Vehicle subclasses based on type │
│ │
│ Observer Pattern → Notify display boards when spot count │
│ changes │
│ │
│ State Pattern → ParkingSpot states (Available, Occupied, │
│ Reserved, Maintenance) │
└──────────────────────────────────────────────────────────────────┘
8. Time Management in a 45-Minute Interview
┌────────────────────────────────────────────────────────────────┐
│ 45-MINUTE LLD INTERVIEW TIME ALLOCATION │
│ │
│ 0:00 ───── 5:00 │ Step 1: Clarify Requirements (5 min) │
│ ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ 5:00 ───── 10:00 │ Step 2: Identify Entities (5 min) │
│ ░░░░░░░░░░████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ 10:00 ───── 20:00 │ Step 3: Define Classes (10 min) │
│ ░░░░░░░░░░░░░░░░░░████████████████░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ 20:00 ───── 28:00 │ Step 4: Relationships (8 min) │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████████████░░░░░░░░░░░░ │
│ │
│ 28:00 ───── 38:00 │ Step 5: Methods + Code (10 min) │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████████████░ │
│ │
│ 38:00 ───── 45:00 │ Step 6: Edge Cases + Wrap Up (7 min) │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████ │
│ │
└────────────────────────────────────────────────────────────────┘
Time Management Tips
| Tip | Why |
|---|---|
| Do NOT skip Step 1 | 3 minutes of clarification saves 10 minutes of redesigning |
| Set a mental checkpoint at 20 min | By 20 minutes, you should have classes defined and be starting relationships |
| Code the happy path first | Get the main flow working before adding error handling |
| Talk while you write | Silence makes interviewers nervous — narrate your thought process |
| If stuck, move on | Spend too long on one class? Move to relationships and come back |
| Save 5 min for edge cases | Mentioning edge cases shows senior-level thinking |
9. Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Jumping straight to code | Misses requirements, needs redesign later | Always start with clarifying questions |
| Too many classes | Overcomplicates the design, runs out of time | Start with 4–6 core classes, expand only if asked |
| Too few classes | Everything in one God class | If a class has 10+ methods, split it |
| Ignoring encapsulation | All fields public | Default to private; add getters only where needed |
| Using inheritance everywhere | Rigid, brittle hierarchy | Favor composition; use inheritance only for clear "is-a" |
| Not using interfaces | Tight coupling between classes | Define interfaces for anything with multiple implementations |
| Perfect code syndrome | Spends 30 min writing perfect syntax | Pseudocode is fine; focus on DESIGN, not syntax |
| Not talking | Interviewer cannot evaluate your thinking | Narrate every decision and trade-off |
| Ignoring the interviewer | Missing hints they are dropping | Watch for "what about X?" — it is a hint, not a question |
| No edge cases | Shows junior-level thinking | Proactively raise 3–5 edge cases at the end |
10. Example Walkthrough — Parking Lot System
Let us walk through the full 6-step framework for "Design a Parking Lot System" as if we are in a live interview.
Step 1: Clarify (minutes 0–5)
"I want to make sure I understand the requirements correctly.
1. Vehicle types: cars, motorcycles, trucks — confirmed.
2. Multiple floors with different spot sizes.
3. Multiple entrances and exits.
4. System issues a ticket on entry, calculates payment on exit.
5. Display board showing available spots per floor.
6. Different pricing per vehicle type.
Let me list what I consider IN scope and OUT of scope:
IN: Vehicle management, spot assignment, ticketing, payment calc
OUT: Actual payment gateway integration, mobile app, reservations
Does this sound right?"
Step 2: Identify Entities (minutes 5–10)
"Let me identify the core entities from these requirements:
Entities:
- ParkingLot — the system itself
- ParkingFloor — a level in the lot
- ParkingSpot — individual parking space
- Vehicle — abstract, with Car/Motorcycle/Truck subtypes
- ParkingTicket — issued on entry, used on exit
- EntrancePanel — where vehicles enter
- ExitPanel — where vehicles exit and pay
- DisplayBoard — shows available spots
Enums:
- VehicleType — MOTORCYCLE, CAR, TRUCK
- SpotSize — SMALL, MEDIUM, LARGE
- TicketStatus — ACTIVE, PAID
Step 3: Define Classes (minutes 10–20)
(Draw class boxes on whiteboard with attributes and key methods — as shown in Steps 3-5 above)
Step 4: Relationships (minutes 20–28)
(Draw the class diagram with arrows, diamonds, and multiplicity — as shown in Step 4 above)
Step 5: Methods (minutes 28–38)
(Write the core methods: enterVehicle, exitVehicle, findAvailableSpot, canFitVehicle, calculatePayment)
Step 6: Edge Cases + Wrap Up (minutes 38–45)
"Let me address some edge cases and extensions:
1. CONCURRENCY: Two cars arriving simultaneously — I would use a
mutex/lock on spot assignment to prevent double-booking.
2. FULL LOT: enterVehicle() checks availability first. Display
board updates via Observer pattern.
3. LOST TICKET: Charge the maximum daily rate. Require license
plate lookup from security camera logs.
4. PRICING FLEXIBILITY: I used a Strategy pattern for payment
calculation so we can swap pricing models (hourly, flat,
weekend) without changing the core logic.
5. EXTENSIBILITY: If we want to add reserved/VIP spots, I would
add a SpotStatus enum (AVAILABLE, OCCUPIED, RESERVED,
MAINTENANCE) and a ReservedSpot subclass.
6. PERSISTENCE: In production, I would persist tickets and spot
state to a database rather than keeping them in memory.
Full Sequence Diagram for Enter Vehicle
┌──────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────┐
│Driver│ │Entrance │ │ParkingLot │ │ Floor │ │ Spot │
└──┬───┘ │ Panel │ └─────┬─────┘ └────┬─────┘ └────┬─────┘
│ └────┬─────┘ │ │ │
│ │ │ │ │
│ arrives │ │ │ │
│───────────►│ │ │ │
│ │ │ │ │
│ │ enterVehicle │ │ │
│ │ (vehicle) │ │ │
│ │──────────────►│ │ │
│ │ │ │ │
│ │ │ findAvailable │ │
│ │ │ Spot(vehicle) │ │
│ │ │──────────────►│ │
│ │ │ │ │
│ │ │ │ canFit │
│ │ │ │ Vehicle(v) │
│ │ │ │─────────────►│
│ │ │ │ │
│ │ │ │ true │
│ │ │ │◄─ ─ ─ ─ ─ ─ │
│ │ │ │ │
│ │ │ spot │ │
│ │ │◄─ ─ ─ ─ ─ ─ ─│ │
│ │ │ │ │
│ │ │ park(vehicle) │
│ │ │─────────────────────────────►│
│ │ │ │ │
│ │ │ create ticket │ │
│ │ │───┐ │ │
│ │ │ │ │ │
│ │ │◄──┘ │ │
│ │ │ │ │
│ │ ticket │ │ │
│ │◄─ ─ ─ ─ ─ ─ ─│ │ │
│ │ │ │ │
│ ticket + │ │ │ │
│ gate opens│ │ │ │
│◄─ ─ ─ ─ ─ │ │ │ │
│ │ │ │ │
11. Key Takeaways
- Follow the 6-step framework religiously — it prevents you from getting lost and gives you a clear structure to fill time productively.
- Clarify first — 3–5 minutes of questions saves 10+ minutes of redesign. Interviewers expect and reward this.
- Nouns become classes, verbs become methods — this extraction technique works for every LLD problem.
- Draw the class diagram before writing code — it forces you to think about relationships and prevents a tangled design.
- Talk constantly — the interviewer cannot read your mind. Narrate every decision: "I am using composition here because rooms cannot exist without the building."
- Mention design patterns — even naming them shows awareness. "I would use Strategy pattern for pricing" earns points.
- Edge cases show seniority — proactively raising concurrency, error handling, and extensibility demonstrates production experience.
- Time management is critical — set mental checkpoints. If you are still clarifying requirements at minute 10, you are behind.
- Do not aim for perfection — a solid design with 6 well-structured classes beats a half-finished design with 15 classes.
Explain-It Challenge
Pick any LLD problem (elevator system, food delivery app, chess game) and practice the 6-step framework out loud, speaking to an imaginary interviewer. Time yourself for 45 minutes. Record yourself if possible — watching the replay reveals habits you did not notice.
Previous → 9.1.d — UML Diagrams
← Back to 9.1 — LLD Foundations (README)