Contract and API Definitions
Introduction: Why Contracts Matter
Section titled “Introduction: Why Contracts Matter”Contracts define how classes interact - they’re the agreements between components. Well-defined contracts lead to:
- ✔ Testable code - Clear interfaces to mock
- ✔ Flexible design - Can swap implementations
- ✔ Clear documentation - Self-documenting code
- ✔ Error prevention - Compile-time checks
Visual: The Power of Contracts
Section titled “Visual: The Power of Contracts”Part 1: Understanding Contracts
Section titled “Part 1: Understanding Contracts”What Are Contracts?
Section titled “What Are Contracts?”A contract defines:
- What a class/method does
- Inputs - Parameters and types
- Outputs - Return types
- Exceptions - What can go wrong
- Preconditions - What must be true before
- Postconditions - What will be true after
Types of Contracts
Section titled “Types of Contracts”- Interface Contracts - Abstract interfaces
- Method Contracts - Method signatures
- API Contracts - REST/HTTP APIs
- Class Contracts - Class responsibilities
Visual: Contract Types
Section titled “Visual: Contract Types”Part 2: Interface Contracts
Section titled “Part 2: Interface Contracts”What Are Interface Contracts?
Section titled “What Are Interface Contracts?”Interface contracts define what a class must implement without specifying how.
Example: Payment Processor Interface
Section titled “Example: Payment Processor Interface”from abc import ABC, abstractmethodfrom typing import Dict, Any
class PaymentProcessor(ABC): """ Interface for payment processing.
Contract: - Must process payments - Must handle refunds - Must validate payment methods """
@abstractmethod def process_payment(self, amount: float, payment_method: str, payment_data: Dict[str, Any]) -> Dict[str, Any]: """ Process a payment.
Args: amount: Payment amount (must be > 0) payment_method: Payment method (e.g., "credit_card", "paypal") payment_data: Payment-specific data (card token, email, etc.)
Returns: Dict containing: - status: "success" or "failed" - transaction_id: Unique transaction ID - message: Optional message
Raises: ValueError: If amount <= 0 or invalid payment_data PaymentFailedException: If payment processing fails """ pass
@abstractmethod def refund_payment(self, transaction_id: str, amount: float) -> Dict[str, Any]: """ Refund a payment.
Args: transaction_id: Original transaction ID amount: Refund amount (must be > 0 and <= original amount)
Returns: Dict containing: - status: "success" or "failed" - refund_id: Unique refund ID
Raises: ValueError: If amount <= 0 or transaction_id invalid RefundFailedException: If refund fails """ pass
@abstractmethod def validate_payment_method(self, payment_method: str) -> bool: """ Validate if payment method is supported.
Args: payment_method: Payment method to validate
Returns: True if supported, False otherwise """ passimport java.util.Map;
/** * Interface for payment processing. * * Contract: * - Must process payments * - Must handle refunds * - Must validate payment methods */public interface PaymentProcessor { /** * Process a payment. * * @param amount Payment amount (must be > 0) * @param paymentMethod Payment method (e.g., "credit_card", "paypal") * @param paymentData Payment-specific data (card token, email, etc.) * @return Map containing status, transaction_id, and optional message * @throws IllegalArgumentException If amount <= 0 or invalid payment_data * @throws PaymentFailedException If payment processing fails */ Map<String, Object> processPayment(double amount, String paymentMethod, Map<String, Object> paymentData);
/** * Refund a payment. * * @param transactionId Original transaction ID * @param amount Refund amount (must be > 0 and <= original amount) * @return Map containing status and refund_id * @throws IllegalArgumentException If amount <= 0 or transaction_id invalid * @throws RefundFailedException If refund fails */ Map<String, Object> refundPayment(String transactionId, double amount);
/** * Validate if payment method is supported. * * @param paymentMethod Payment method to validate * @return True if supported, False otherwise */ boolean validatePaymentMethod(String paymentMethod);}Visual: Interface Contract
Section titled “Visual: Interface Contract”Key Elements of Interface Contracts
Section titled “Key Elements of Interface Contracts”- Method signatures - What methods exist
- Parameter types - What inputs are expected
- Return types - What outputs are guaranteed
- Exceptions - What can go wrong
- Documentation - Clear description of behavior
Part 3: Method Contracts
Section titled “Part 3: Method Contracts”What Are Method Contracts?
Section titled “What Are Method Contracts?”Method contracts define the behavior of individual methods - inputs, outputs, and exceptions.
Example: ParkingLot Methods
Section titled “Example: ParkingLot Methods”from typing import Optionalfrom datetime import datetime
class ParkingLot: """ Manages parking operations. """
def park_vehicle(self, vehicle: Vehicle) -> Ticket: """ Parks a vehicle and returns a ticket.
Contract: - Precondition: Vehicle must be valid and parking lot must have available spots - Postcondition: Vehicle is parked, ticket is issued, spot is occupied
Args: vehicle: Vehicle to park (must not be None)
Returns: Ticket: Parking ticket with entry details
Raises: ValueError: If vehicle is None or invalid ParkingLotFullException: If no spots available for vehicle type InvalidVehicleException: If vehicle type not supported
Example: >>> vehicle = Car("ABC123") >>> ticket = parking_lot.park_vehicle(vehicle) >>> print(ticket.ticket_id) "TICKET_12345" """ if vehicle is None: raise ValueError("Vehicle cannot be None")
if not self._is_vehicle_type_supported(vehicle.get_vehicle_type()): raise InvalidVehicleException(f"Vehicle type {vehicle.get_vehicle_type()} not supported")
spot = self._find_available_spot(vehicle.get_vehicle_type()) if spot is None: raise ParkingLotFullException("No available spots")
spot.park_vehicle(vehicle) ticket = Ticket(vehicle, spot, datetime.now()) self._tickets[ticket.ticket_id] = ticket
return ticket
def unpark_vehicle(self, ticket_id: str) -> Payment: """ Unparks a vehicle and processes payment.
Contract: - Precondition: Ticket must be valid and vehicle must be parked - Postcondition: Vehicle is unparked, spot is available, payment is processed
Args: ticket_id: Parking ticket ID (must not be None or empty)
Returns: Payment: Payment transaction details
Raises: ValueError: If ticket_id is None or empty InvalidTicketException: If ticket is invalid or already used VehicleNotParkedException: If vehicle is not currently parked
Example: >>> payment = parking_lot.unpark_vehicle("TICKET_12345") >>> print(payment.amount) 25.50 """ if not ticket_id or ticket_id.strip() == "": raise ValueError("Ticket ID cannot be None or empty")
if ticket_id not in self._tickets: raise InvalidTicketException(f"Ticket {ticket_id} not found")
ticket = self._tickets[ticket_id]
if ticket.is_returned(): raise InvalidTicketException(f"Ticket {ticket_id} already used")
spot = ticket.spot vehicle = spot.unpark_vehicle()
duration = ticket.calculate_duration() amount = self._calculate_payment(duration, vehicle.get_vehicle_type())
payment = Payment(ticket, amount, datetime.now()) ticket.mark_as_returned()
return payment
def find_available_spots(self, vehicle_type: VehicleType) -> List[ParkingSpot]: """ Finds all available spots for a vehicle type.
Contract: - Precondition: Vehicle type must be valid - Postcondition: Returns list of available spots (may be empty)
Args: vehicle_type: Type of vehicle (must not be None)
Returns: List[ParkingSpot]: List of available spots (empty if none available)
Raises: ValueError: If vehicle_type is None
Example: >>> spots = parking_lot.find_available_spots(VehicleType.CAR) >>> print(len(spots)) 5 """ if vehicle_type is None: raise ValueError("Vehicle type cannot be None")
return [spot for spot in self._spots if spot.is_available() and spot.get_type() == vehicle_type]import java.util.List;import java.util.Optional;
public class ParkingLot { /** * Parks a vehicle and returns a ticket. * * Contract: * - Precondition: Vehicle must be valid and parking lot must have available spots * - Postcondition: Vehicle is parked, ticket is issued, spot is occupied * * @param vehicle Vehicle to park (must not be null) * @return Parking ticket with entry details * @throws IllegalArgumentException If vehicle is null or invalid * @throws ParkingLotFullException If no spots available for vehicle type * @throws InvalidVehicleException If vehicle type not supported */ public Ticket parkVehicle(Vehicle vehicle) { if (vehicle == null) { throw new IllegalArgumentException("Vehicle cannot be null"); }
if (!isVehicleTypeSupported(vehicle.getVehicleType())) { throw new InvalidVehicleException("Vehicle type not supported"); }
ParkingSpot spot = findAvailableSpot(vehicle.getVehicleType()) .orElseThrow(() -> new ParkingLotFullException("No available spots"));
spot.parkVehicle(vehicle); Ticket ticket = new Ticket(vehicle, spot, LocalDateTime.now()); tickets.put(ticket.getTicketId(), ticket);
return ticket; }
/** * Unparks a vehicle and processes payment. * * Contract: * - Precondition: Ticket must be valid and vehicle must be parked * - Postcondition: Vehicle is unparked, spot is available, payment is processed * * @param ticketId Parking ticket ID (must not be null or empty) * @return Payment transaction details * @throws IllegalArgumentException If ticket_id is null or empty * @throws InvalidTicketException If ticket is invalid or already used */ public Payment unparkVehicle(String ticketId) { if (ticketId == null || ticketId.trim().isEmpty()) { throw new IllegalArgumentException("Ticket ID cannot be null or empty"); }
Ticket ticket = tickets.get(ticketId); if (ticket == null) { throw new InvalidTicketException("Ticket not found"); }
if (ticket.isReturned()) { throw new InvalidTicketException("Ticket already used"); }
ParkingSpot spot = ticket.getSpot(); Vehicle vehicle = spot.unparkVehicle();
double duration = ticket.calculateDuration(); double amount = calculatePayment(duration, vehicle.getVehicleType());
Payment payment = new Payment(ticket, amount, LocalDateTime.now()); ticket.markAsReturned();
return payment; }
/** * Finds all available spots for a vehicle type. * * Contract: * - Precondition: Vehicle type must be valid * - Postcondition: Returns list of available spots (may be empty) * * @param vehicleType Type of vehicle (must not be null) * @return List of available spots (empty if none available) * @throws IllegalArgumentException If vehicle_type is null */ public List<ParkingSpot> findAvailableSpots(VehicleType vehicleType) { if (vehicleType == null) { throw new IllegalArgumentException("Vehicle type cannot be null"); }
return spots.stream() .filter(spot -> spot.isAvailable() && spot.getType() == vehicleType) .collect(Collectors.toList()); }}Key Elements of Method Contracts
Section titled “Key Elements of Method Contracts”- Method signature - Name, parameters, return type
- Preconditions - What must be true before calling
- Postconditions - What will be true after calling
- Exceptions - What can go wrong
- Documentation - Clear description
Visual: Method Contract Elements
Section titled “Visual: Method Contract Elements”Part 4: API Contracts (REST APIs)
Section titled “Part 4: API Contracts (REST APIs)”What Are API Contracts?
Section titled “What Are API Contracts?”API contracts define how external systems interact with your system via HTTP/REST APIs.
Example: Parking Lot API
Section titled “Example: Parking Lot API”# OpenAPI/Swagger Specification
paths: /api/v1/parking/park: post: summary: Park a vehicle description: Parks a vehicle and returns a parking ticket requestBody: required: true content: application/json: schema: type: object required: - vehicleType - licensePlate properties: vehicleType: type: string enum: [CAR, MOTORCYCLE, TRUCK] description: Type of vehicle licensePlate: type: string minLength: 1 maxLength: 20 description: Vehicle license plate responses: '200': description: Vehicle parked successfully content: application/json: schema: type: object properties: ticketId: type: string example: "TICKET_12345" entryTime: type: string format: date-time spotId: type: string example: "SPOT_A1" '400': description: Bad request (invalid input) content: application/json: schema: type: object properties: error: type: string example: "Invalid vehicle type" '404': description: No available spots content: application/json: schema: type: object properties: error: type: string example: "Parking lot is full"
/api/v1/parking/unpark: post: summary: Unpark a vehicle description: Unparks a vehicle and processes payment requestBody: required: true content: application/json: schema: type: object required: - ticketId properties: ticketId: type: string description: Parking ticket ID responses: '200': description: Vehicle unparked successfully content: application/json: schema: type: object properties: paymentId: type: string example: "PAYMENT_67890" amount: type: number format: float example: 25.50 duration: type: number format: float example: 2.5 '400': description: Bad request '404': description: Ticket not found
/api/v1/parking/spots: get: summary: Get available spots description: Returns available parking spots for a vehicle type parameters: - name: vehicleType in: query required: true schema: type: string enum: [CAR, MOTORCYCLE, TRUCK] responses: '200': description: List of available spots content: application/json: schema: type: object properties: spots: type: array items: type: object properties: spotId: type: string spotType: type: string location: type: stringfrom flask import Flask, request, jsonifyfrom typing import Dict, Any
app = Flask(__name__)
@app.route('/api/v1/parking/park', methods=['POST'])def park_vehicle(): """ API Contract: - Endpoint: POST /api/v1/parking/park - Request Body: { vehicleType: str, licensePlate: str } - Response 200: { ticketId: str, entryTime: str, spotId: str } - Response 400: { error: str } - Bad request - Response 404: { error: str } - No spots available """ data = request.get_json()
# Validate input if not data or 'vehicleType' not in data or 'licensePlate' not in data: return jsonify({'error': 'Missing required fields'}), 400
vehicle_type = data['vehicleType'] license_plate = data['licensePlate']
# Validate vehicle type if vehicle_type not in ['CAR', 'MOTORCYCLE', 'TRUCK']: return jsonify({'error': 'Invalid vehicle type'}), 400
try: # Create vehicle and park vehicle = create_vehicle(vehicle_type, license_plate) ticket = parking_lot.park_vehicle(vehicle)
return jsonify({ 'ticketId': ticket.ticket_id, 'entryTime': ticket.entry_time.isoformat(), 'spotId': ticket.spot.spot_id }), 200
except ParkingLotFullException as e: return jsonify({'error': str(e)}), 404 except Exception as e: return jsonify({'error': str(e)}), 400
@app.route('/api/v1/parking/unpark', methods=['POST'])def unpark_vehicle(): """ API Contract: - Endpoint: POST /api/v1/parking/unpark - Request Body: { ticketId: str } - Response 200: { paymentId: str, amount: float, duration: float } - Response 400: { error: str } - Bad request - Response 404: { error: str } - Ticket not found """ data = request.get_json()
if not data or 'ticketId' not in data: return jsonify({'error': 'Missing ticketId'}), 400
ticket_id = data['ticketId']
try: payment = parking_lot.unpark_vehicle(ticket_id)
return jsonify({ 'paymentId': payment.payment_id, 'amount': payment.amount, 'duration': payment.ticket.calculate_duration() }), 200
except InvalidTicketException as e: return jsonify({'error': str(e)}), 404 except Exception as e: return jsonify({'error': str(e)}), 400
@app.route('/api/v1/parking/spots', methods=['GET'])def get_available_spots(): """ API Contract: - Endpoint: GET /api/v1/parking/spots?vehicleType=CAR - Query Parameter: vehicleType (required) - Response 200: { spots: [{ spotId: str, spotType: str, location: str }] } - Response 400: { error: str } - Invalid vehicle type """ vehicle_type = request.args.get('vehicleType')
if not vehicle_type: return jsonify({'error': 'Missing vehicleType parameter'}), 400
if vehicle_type not in ['CAR', 'MOTORCYCLE', 'TRUCK']: return jsonify({'error': 'Invalid vehicle type'}), 400
spots = parking_lot.find_available_spots(VehicleType[vehicle_type])
return jsonify({ 'spots': [{ 'spotId': spot.spot_id, 'spotType': spot.spot_type.value, 'location': spot.location } for spot in spots] }), 200import org.springframework.web.bind.annotation.*;import org.springframework.http.ResponseEntity;import java.util.*;
@RestController@RequestMapping("/api/v1/parking")public class ParkingController {
@PostMapping("/park") public ResponseEntity<?> parkVehicle(@RequestBody Map<String, String> data) { /* API Contract: - Endpoint: POST /api/v1/parking/park - Request Body: { vehicleType: str, licensePlate: str } - Response 200: { ticketId: str, entryTime: str, spotId: str } - Response 400: { error: str } - Bad request - Response 404: { error: str } - No spots available */
if (data == null || !data.containsKey("vehicleType") || !data.containsKey("licensePlate")) { return ResponseEntity.badRequest().body(Map.of("error", "Missing required fields")); }
String vehicleType = data.get("vehicleType"); String licensePlate = data.get("licensePlate");
// Validate vehicle type if (!isValidVehicleType(vehicleType)) { return ResponseEntity.badRequest().body(Map.of("error", "Invalid vehicle type")); }
try { Vehicle vehicle = createVehicle(vehicleType, licensePlate); Ticket ticket = parkingLot.parkVehicle(vehicle);
Map<String, Object> response = new HashMap<>(); response.put("ticketId", ticket.getTicketId()); response.put("entryTime", ticket.getEntryTime().toString()); response.put("spotId", ticket.getSpot().getSpotId());
return ResponseEntity.ok(response);
} catch (ParkingLotFullException e) { return ResponseEntity.status(404).body(Map.of("error", e.getMessage())); } catch (Exception e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } }
@PostMapping("/unpark") public ResponseEntity<?> unparkVehicle(@RequestBody Map<String, String> data) { /* API Contract: - Endpoint: POST /api/v1/parking/unpark - Request Body: { ticketId: str } - Response 200: { paymentId: str, amount: float, duration: float } - Response 400: { error: str } - Bad request - Response 404: { error: str } - Ticket not found */
if (data == null || !data.containsKey("ticketId")) { return ResponseEntity.badRequest().body(Map.of("error", "Missing ticketId")); }
String ticketId = data.get("ticketId");
try { Payment payment = parkingLot.unparkVehicle(ticketId);
Map<String, Object> response = new HashMap<>(); response.put("paymentId", payment.getPaymentId()); response.put("amount", payment.getAmount()); response.put("duration", payment.getTicket().calculateDuration());
return ResponseEntity.ok(response);
} catch (InvalidTicketException e) { return ResponseEntity.status(404).body(Map.of("error", e.getMessage())); } catch (Exception e) { return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } }
@GetMapping("/spots") public ResponseEntity<?> getAvailableSpots(@RequestParam String vehicleType) { /* API Contract: - Endpoint: GET /api/v1/parking/spots?vehicleType=CAR - Query Parameter: vehicleType (required) - Response 200: { spots: [{ spotId: str, spotType: str, location: str }] } - Response 400: { error: str } - Invalid vehicle type */
if (vehicleType == null || vehicleType.isEmpty()) { return ResponseEntity.badRequest().body(Map.of("error", "Missing vehicleType parameter")); }
if (!isValidVehicleType(vehicleType)) { return ResponseEntity.badRequest().body(Map.of("error", "Invalid vehicle type")); }
List<ParkingSpot> spots = parkingLot.findAvailableSpots(VehicleType.valueOf(vehicleType));
List<Map<String, String>> spotsList = new ArrayList<>(); for (ParkingSpot spot : spots) { Map<String, String> spotData = new HashMap<>(); spotData.put("spotId", spot.getSpotId()); spotData.put("spotType", spot.getSpotType().toString()); spotData.put("location", spot.getLocation()); spotsList.add(spotData); }
return ResponseEntity.ok(Map.of("spots", spotsList)); }}- Endpoint - URL and HTTP method
- Request - Body, parameters, headers
- Response - Status codes, body format
- Error handling - Error codes and messages
- Authentication - How to authenticate
Visual: API Contract Structure
Section titled “Visual: API Contract Structure”Part 5: Exception Handling in Contracts
Section titled “Part 5: Exception Handling in Contracts”Why Exceptions Matter
Section titled “Why Exceptions Matter”Exceptions are part of the contract - they define what can go wrong and how to handle it.
Exception Types
Section titled “Exception Types”- Validation Exceptions - Invalid input
- Business Logic Exceptions - Business rule violations
- System Exceptions - System failures
- Not Found Exceptions - Resource not found
Example: Custom Exceptions
Section titled “Example: Custom Exceptions”class ParkingLotException(Exception): """Base exception for parking lot operations""" pass
class ParkingLotFullException(ParkingLotException): """Raised when parking lot is full""" pass
class InvalidVehicleException(ParkingLotException): """Raised when vehicle type is invalid""" pass
class InvalidTicketException(ParkingLotException): """Raised when ticket is invalid""" pass
class VehicleNotParkedException(ParkingLotException): """Raised when vehicle is not currently parked""" pass
# Usage in contractdef park_vehicle(self, vehicle: Vehicle) -> Ticket: """ Parks a vehicle.
Raises: ValueError: If vehicle is None InvalidVehicleException: If vehicle type not supported ParkingLotFullException: If no spots available """ if vehicle is None: raise ValueError("Vehicle cannot be None")
if not self._is_vehicle_type_supported(vehicle.get_vehicle_type()): raise InvalidVehicleException(f"Vehicle type {vehicle.get_vehicle_type()} not supported")
spot = self._find_available_spot(vehicle.get_vehicle_type()) if spot is None: raise ParkingLotFullException("No available spots")
# ... rest of implementationpublic class ParkingLotException extends Exception { public ParkingLotException(String message) { super(message); }}
public class ParkingLotFullException extends ParkingLotException { public ParkingLotFullException(String message) { super(message); }}
public class InvalidVehicleException extends ParkingLotException { public InvalidVehicleException(String message) { super(message); }}
public class InvalidTicketException extends ParkingLotException { public InvalidTicketException(String message) { super(message); }}
// Usage in contractpublic Ticket parkVehicle(Vehicle vehicle) throws InvalidVehicleException, ParkingLotFullException { if (vehicle == null) { throw new IllegalArgumentException("Vehicle cannot be null"); }
if (!isVehicleTypeSupported(vehicle.getVehicleType())) { throw new InvalidVehicleException("Vehicle type not supported"); }
ParkingSpot spot = findAvailableSpot(vehicle.getVehicleType()) .orElseThrow(() -> new ParkingLotFullException("No available spots"));
// ... rest of implementation}Part 6: Best Practices
Section titled “Part 6: Best Practices”✔ Be explicit - Clear parameter and return types
✔ Document exceptions - What can go wrong?
✔ Use meaningful names - Self-documenting code
✔ Define preconditions - What must be true before?
✔ Define postconditions - What will be true after?
✔ Handle edge cases - Null checks, validation
✔ Use interfaces - For abstraction and flexibility
Don’ts
Section titled “Don’ts”❌ Don’t be vague - Unclear contracts lead to bugs
❌ Don’t forget exceptions - Document what can fail
❌ Don’t expose implementation - Hide internal details
❌ Don’t break contracts - Once defined, maintain them
❌ Don’t over-complicate - Keep contracts simple
Visual: Good vs Bad Contracts
Section titled “Visual: Good vs Bad Contracts”Summary: Contract and API Definitions
Section titled “Summary: Contract and API Definitions”Key Takeaways
Section titled “Key Takeaways”✔ Contracts define agreements - What classes/methods will do
✔ Interfaces - Abstract contracts for flexibility
✔ Method contracts - Preconditions, postconditions, exceptions
✔ API contracts - REST/HTTP API specifications
✔ Exception handling - Part of the contract
✔ Documentation - Clear and comprehensive
✔ Best practices - Explicit, documented, maintainable
Contract Checklist
Section titled “Contract Checklist”When defining contracts, ensure:
- Clear signatures - Parameters and return types
- Documented exceptions - What can go wrong?
- Preconditions - What must be true before?
- Postconditions - What will be true after?
- Well documented - Clear descriptions
- Edge cases handled - Null checks, validation
- Consistent - Follow same patterns
Visual Summary
Section titled “Visual Summary”Complete LLD Interview Process
Section titled “Complete LLD Interview Process”Congratulations! You’ve now mastered all the key steps of LLD interviews:
- ✔ Understanding LLD Interviews - What they are and why they matter
- ✔ Steps in LLD Interview - The systematic approach
- ✔ Identifying Actors & Entities - Foundation of design
- ✔ Assign Responsibilities - Single Responsibility Principle
- ✔ Class Diagrams - Visualize your design
- ✔ Contract and API Definitions - Define interfaces
Final Checklist
Section titled “Final Checklist”Before your LLD interview, make sure you can:
- Understand the problem - Ask clarifying questions
- Identify actors - Who uses the system?
- Identify entities - What are the core objects?
- Assign responsibilities - One per class
- Create class diagrams - Visualize relationships
- Define contracts - Clear interfaces
- Handle edge cases - Think about errors
- Implement cleanly - Follow your design