0% found this document useful (0 votes)
2 views26 pages

Low Level Design Programming in Python_ From Basic

This document provides a comprehensive guide on Low Level Design (LLD) programming in Python, covering fundamental concepts to advanced implementation skills. It emphasizes key topics such as object-oriented programming, SOLID principles, design patterns, and practical projects like a parking lot system. The guide includes code examples and explanations to facilitate understanding of LLD concepts and their applications in Python.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views26 pages

Low Level Design Programming in Python_ From Basic

This document provides a comprehensive guide on Low Level Design (LLD) programming in Python, covering fundamental concepts to advanced implementation skills. It emphasizes key topics such as object-oriented programming, SOLID principles, design patterns, and practical projects like a parking lot system. The guide includes code examples and explanations to facilitate understanding of LLD concepts and their applications in Python.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

Low Level Design Programming in Python: From

Basics to Expert Level


Welcome to your comprehensive journey into Low Level Design (LLD) programming in Python!
This guide will take you from fundamental concepts to advanced implementation skills through
hands-on projects and real-world examples.

What is Low Level Design?


Low Level Design (LLD) focuses on designing the detailed components and interactions within a
system. Unlike High Level Design which deals with overall architecture, LLD translates
requirements into implementable class structures, algorithms, and interfaces[1]. LLD serves as
the bridge between high-level system requirements and actual code implementation[2].
In Python, LLD emphasizes:
Class modeling and object relationships
Design patterns implementation
SOLID principles application
Component interactions and data flow
Algorithm design for specific functionality

Foundation: Object-Oriented Programming in Python

Core OOP Concepts


Classes and Objects[3][4]
Classes serve as blueprints for creating objects that encapsulate both data (attributes) and
functionality (methods).

class Vehicle:
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
self._engine_status = False # Protected attribute

def start_engine(self):
if not self._engine_status:
self._engine_status = True
return f"{self.brand} {self.model} engine started"
return "Engine already running"
def stop_engine(self):
if self._engine_status:
self._engine_status = False
return "Engine stopped"
return "Engine already off"

def __str__(self):
return f"{self.year} {self.brand} {self.model}"

# Creating objects
car = Vehicle("Toyota", "Camry", 2023)
print(car.start_engine()) # Toyota Camry engine started

Inheritance and Polymorphism[5][6]

class Car(Vehicle):
def __init__(self, brand, model, year, doors):
super().__init__(brand, model, year)
self.doors = doors

def honk(self):
return "Beep beep!"

class Motorcycle(Vehicle):
def __init__(self, brand, model, year, engine_cc):
super().__init__(brand, model, year)
self.engine_cc = engine_cc

def honk(self):
return "Vroom vroom!"

# Polymorphism in action
vehicles = [
Car("Honda", "Civic", 2023, 4),
Motorcycle("Yamaha", "R1", 2023, 1000)
]

for vehicle in vehicles:


print(f"{vehicle} says: {vehicle.honk()}")

Encapsulation[3][7]

class BankAccount:
def __init__(self, account_number, initial_balance=0):
self._account_number = account_number # Protected
self.__balance = initial_balance # Private
self._transaction_history = []

def deposit(self, amount):


if amount > 0:
self.__balance += amount
self._transaction_history.append(f"Deposited: ${amount}")
return True
return False

def withdraw(self, amount):


if 0 < amount <= self.__balance:
self.__balance -= amount
self._transaction_history.append(f"Withdrew: ${amount}")
return True
return False

def get_balance(self):
return self.__balance

@property
def account_number(self):
return self._account_number

SOLID Principles Implementation


The SOLID principles form the foundation of good object-oriented design[8][9].

1. Single Responsibility Principle (SRP)


Each class should have only one reason to change[10][11].

# Violates SRP
class BadUser:
def __init__(self, username, email):
self.username = username
self.email = email

def save_to_database(self):
# Database logic
pass

def send_email(self):
# Email logic
pass

# Follows SRP
class User:
def __init__(self, username, email):
self.username = username
self.email = email

class UserRepository:
def save_user(self, user):
# Database logic
print(f"Saving {user.username} to database")

class EmailService:
def send_welcome_email(self, user):
# Email logic
print(f"Sending welcome email to {user.email}")
2. Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification[10][8].

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount):
pass

class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via Credit Card"

class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount):
return f"Processing ${amount} via PayPal"

class BitcoinProcessor(PaymentProcessor): # Extension without modification


def process_payment(self, amount):
return f"Processing ${amount} via Bitcoin"

class PaymentManager:
def __init__(self, processor: PaymentProcessor):
self.processor = processor

def make_payment(self, amount):


return self.processor.process_payment(amount)

3. Liskov Substitution Principle (LSP)


Objects of a superclass should be replaceable with objects of its subclasses[10][12].

class Bird(ABC):
@abstractmethod
def make_sound(self):
pass

class FlyingBird(Bird):
@abstractmethod
def fly(self):
pass

def make_sound(self):
return "Generic bird sound"

class Sparrow(FlyingBird):
def fly(self):
return "Sparrow flying high"

def make_sound(self):
return "Chirp chirp"
class Penguin(Bird): # Penguins can't fly, so they don't inherit from FlyingBird
def make_sound(self):
return "Penguin sound"

def swim(self):
return "Penguin swimming"

4. Interface Segregation Principle (ISP)


Clients should not be forced to depend on interfaces they don't use[10][11].

from abc import ABC, abstractmethod

# Violates ISP
class BadWorker(ABC):
@abstractmethod
def work(self):
pass

@abstractmethod
def eat(self):
pass

# Follows ISP
class Worker(ABC):
@abstractmethod
def work(self):
pass

class Eater(ABC):
@abstractmethod
def eat(self):
pass

class Human(Worker, Eater):


def work(self):
return "Human working"

def eat(self):
return "Human eating"

class Robot(Worker): # Robots work but don't eat


def work(self):
return "Robot working"

5. Dependency Inversion Principle (DIP)


High-level modules should not depend on low-level modules; both should depend on
abstractions[10][9].

class EmailSender(ABC):
@abstractmethod
def send_email(self, recipient, message):
pass

class SMTPEmailSender(EmailSender):
def send_email(self, recipient, message):
return f"Sending via SMTP to {recipient}: {message}"

class SendGridEmailSender(EmailSender):
def send_email(self, recipient, message):
return f"Sending via SendGrid to {recipient}: {message}"

class NotificationService: # High-level module


def __init__(self, email_sender: EmailSender):
self.email_sender = email_sender # Depends on abstraction

def notify_user(self, user_email, notification):


return self.email_sender.send_email(user_email, notification)

# Usage
smtp_sender = SMTPEmailSender()
notification_service = NotificationService(smtp_sender)
result = notification_service.notify_user("[email protected]", "Welcome!")

Design Patterns in Python


Design patterns provide reusable solutions to common programming problems[13][14].

Creational Patterns
Factory Pattern[13][15]

from abc import ABC, abstractmethod


from enum import Enum

class VehicleType(Enum):
CAR = "car"
MOTORCYCLE = "motorcycle"
TRUCK = "truck"

class Vehicle(ABC):
@abstractmethod
def start(self):
pass

@abstractmethod
def get_info(self):
pass

class Car(Vehicle):
def __init__(self, model):
self.model = model

def start(self):
return f"Car {self.model} engine started"
def get_info(self):
return f"Car: {self.model}"

class Motorcycle(Vehicle):
def __init__(self, model):
self.model = model

def start(self):
return f"Motorcycle {self.model} engine revved"

def get_info(self):
return f"Motorcycle: {self.model}"

class VehicleFactory:
@staticmethod
def create_vehicle(vehicle_type: VehicleType, model: str) -> Vehicle:
if vehicle_type == VehicleType.CAR:
return Car(model)
elif vehicle_type == VehicleType.MOTORCYCLE:
return Motorcycle(model)
else:
raise ValueError(f"Unknown vehicle type: {vehicle_type}")

# Usage
factory = VehicleFactory()
car = factory.create_vehicle(VehicleType.CAR, "Tesla Model 3")
motorcycle = factory.create_vehicle(VehicleType.MOTORCYCLE, "Harley Davidson")

print(car.start()) # Car Tesla Model 3 engine started


print(motorcycle.start()) # Motorcycle Harley Davidson engine revved

Singleton Pattern[13][14]

class DatabaseConnection:
_instance = None
_connection = None

def __new__(cls):
if cls._instance is None:
cls._instance = super(DatabaseConnection, cls).__new__(cls)
return cls._instance

def connect(self):
if not self._connection:
self._connection = "Connected to database"
print("Database connection established")
return self._connection

def get_connection(self):
return self._connection

# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True - same instance
db1.connect()
print(db2.get_connection()) # Connected to database

Behavioral Patterns
Observer Pattern[13][14]

class Subject:
def __init__(self):
self._observers = []
self._state = None

def attach(self, observer):


self._observers.append(observer)

def detach(self, observer):


self._observers.remove(observer)

def notify(self):
for observer in self._observers:
observer.update(self._state)

def set_state(self, state):


self._state = state
self.notify()

class Observer(ABC):
@abstractmethod
def update(self, state):
pass

class EmailNotifier(Observer):
def update(self, state):
print(f"Email notification: State changed to {state}")

class SMSNotifier(Observer):
def update(self, state):
print(f"SMS notification: State changed to {state}")

# Usage
subject = Subject()
email_notifier = EmailNotifier()
sms_notifier = SMSNotifier()

subject.attach(email_notifier)
subject.attach(sms_notifier)

subject.set_state("Order Confirmed")
# Email notification: State changed to Order Confirmed
# SMS notification: State changed to Order Confirmed

Strategy Pattern[13][16]
class SortingStrategy(ABC):
@abstractmethod
def sort(self, data):
pass

class BubbleSortStrategy(SortingStrategy):
def sort(self, data):
data_copy = data.copy()
n = len(data_copy)
for i in range(n):
for j in range(0, n - i - 1):
if data_copy[j] > data_copy[j + 1]:
data_copy[j], data_copy[j + 1] = data_copy[j + 1], data_copy[j]
return data_copy

class QuickSortStrategy(SortingStrategy):
def sort(self, data):
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)

class SortContext:
def __init__(self, strategy: SortingStrategy):
self._strategy = strategy

def set_strategy(self, strategy: SortingStrategy):


self._strategy = strategy

def sort_data(self, data):


return self._strategy.sort(data)

# Usage
data = [64, 34, 25, 12, 22, 11, 90]
context = SortContext(BubbleSortStrategy())
print(context.sort_data(data)) # Bubble sort result

context.set_strategy(QuickSortStrategy())
print(context.sort_data(data)) # Quick sort result

Hands-On Project 1: Parking Lot System


Let's implement a comprehensive parking lot system that demonstrates LLD principles[17][18]
[19].
Requirements Analysis
Multiple parking levels with different spot types
Support for different vehicle types
Payment processing system
Entry/exit management
Real-time availability tracking

Class Design

from datetime import datetime


from enum import Enum
from abc import ABC, abstractmethod
import uuid

class VehicleType(Enum):
MOTORCYCLE = "motorcycle"
CAR = "car"
TRUCK = "truck"

class SpotType(Enum):
MOTORCYCLE_SPOT = "motorcycle"
COMPACT_SPOT = "compact"
LARGE_SPOT = "large"

class SpotStatus(Enum):
AVAILABLE = "available"
OCCUPIED = "occupied"
RESERVED = "reserved"

class Vehicle:
def __init__(self, license_plate: str, vehicle_type: VehicleType):
self.license_plate = license_plate
self.vehicle_type = vehicle_type

def __str__(self):
return f"{self.vehicle_type.value}: {self.license_plate}"

class ParkingSpot:
def __init__(self, spot_id: str, spot_type: SpotType):
self.spot_id = spot_id
self.spot_type = spot_type
self.status = SpotStatus.AVAILABLE
self.vehicle = None
self.parked_time = None

def can_park(self, vehicle: Vehicle) -> bool:


if self.status != SpotStatus.AVAILABLE:
return False

# Check if vehicle fits in spot


vehicle_spot_mapping = {
VehicleType.MOTORCYCLE: [SpotType.MOTORCYCLE_SPOT, SpotType.COMPACT_SPOT, Spo
VehicleType.CAR: [SpotType.COMPACT_SPOT, SpotType.LARGE_SPOT],
VehicleType.TRUCK: [SpotType.LARGE_SPOT]
}

return self.spot_type in vehicle_spot_mapping[vehicle.vehicle_type]

def park_vehicle(self, vehicle: Vehicle) -> bool:


if self.can_park(vehicle):
self.vehicle = vehicle
self.status = SpotStatus.OCCUPIED
self.parked_time = datetime.now()
return True
return False

def remove_vehicle(self) -> Vehicle:


parked_vehicle = self.vehicle
self.vehicle = None
self.status = SpotStatus.AVAILABLE
self.parked_time = None
return parked_vehicle

class ParkingLevel:
def __init__(self, level_id: str):
self.level_id = level_id
self.spots = {}
self._init_spots()

def _init_spots(self):
# Initialize spots for the level
for i in range(10): # 10 motorcycle spots
spot_id = f"{self.level_id}-M{i}"
self.spots[spot_id] = ParkingSpot(spot_id, SpotType.MOTORCYCLE_SPOT)

for i in range(20): # 20 compact spots


spot_id = f"{self.level_id}-C{i}"
self.spots[spot_id] = ParkingSpot(spot_id, SpotType.COMPACT_SPOT)

for i in range(5): # 5 large spots


spot_id = f"{self.level_id}-L{i}"
self.spots[spot_id] = ParkingSpot(spot_id, SpotType.LARGE_SPOT)

def find_available_spot(self, vehicle: Vehicle) -> ParkingSpot:


for spot in self.spots.values():
if spot.can_park(vehicle):
return spot
return None

def get_available_spots_count(self) -> dict:


count = {spot_type: 0 for spot_type in SpotType}
for spot in self.spots.values():
if spot.status == SpotStatus.AVAILABLE:
count[spot.spot_type] += 1
return count

class ParkingTicket:
def __init__(self, ticket_id: str, vehicle: Vehicle, spot: ParkingSpot):
self.ticket_id = ticket_id
self.vehicle = vehicle
self.spot = spot
self.issued_time = datetime.now()
self.paid_time = None
self.is_paid = False

def calculate_fee(self) -> float:


if self.paid_time:
duration = self.paid_time - self.issued_time
else:
duration = datetime.now() - self.issued_time

hours = max(1, duration.total_seconds() / 3600) # Minimum 1 hour

# Different rates based on spot type


rates = {
SpotType.MOTORCYCLE_SPOT: 2.0,
SpotType.COMPACT_SPOT: 3.0,
SpotType.LARGE_SPOT: 5.0
}

return hours * rates[self.spot.spot_type]

# Payment Strategy Pattern Implementation


class PaymentStrategy(ABC):
@abstractmethod
def process_payment(self, amount: float) -> bool:
pass

class CashPayment(PaymentStrategy):
def process_payment(self, amount: float) -> bool:
print(f"Processing cash payment of ${amount:.2f}")
return True

class CardPayment(PaymentStrategy):
def __init__(self, card_number: str):
self.card_number = card_number

def process_payment(self, amount: float) -> bool:


print(f"Processing card payment of ${amount:.2f} using card {self.card_number[-4:
return True

class ParkingLot:
def __init__(self, name: str, num_levels: int):
self.name = name
self.levels = {}
self.tickets = {}

# Initialize levels
for i in range(num_levels):
level_id = f"Level-{i+1}"
self.levels[level_id] = ParkingLevel(level_id)

def park_vehicle(self, vehicle: Vehicle) -> ParkingTicket:


# Find available spot across all levels
for level in self.levels.values():
spot = level.find_available_spot(vehicle)
if spot:
if spot.park_vehicle(vehicle):
ticket_id = str(uuid.uuid4())
ticket = ParkingTicket(ticket_id, vehicle, spot)
self.tickets[ticket_id] = ticket
print(f"Vehicle {vehicle} parked at {spot.spot_id}")
return ticket

print(f"No available spot for vehicle {vehicle}")


return None

def exit_vehicle(self, ticket_id: str, payment_strategy: PaymentStrategy) -> bool:


if ticket_id not in self.tickets:
print("Invalid ticket")
return False

ticket = self.tickets[ticket_id]

if not ticket.is_paid:
fee = ticket.calculate_fee()
if payment_strategy.process_payment(fee):
ticket.is_paid = True
ticket.paid_time = datetime.now()
else:
print("Payment failed")
return False

# Remove vehicle from spot


removed_vehicle = ticket.spot.remove_vehicle()
print(f"Vehicle {removed_vehicle} exited from {ticket.spot.spot_id}")

# Remove ticket
del self.tickets[ticket_id]
return True

def get_parking_status(self):
total_spots = 0
available_spots = 0

print(f"\n=== {self.name} Parking Status ===")


for level_id, level in self.levels.items():
available_count = level.get_available_spots_count()
level_total = sum(len([s for s in level.spots.values() if s.spot_type == spot
for spot_type in SpotType)
level_available = sum(available_count.values())

print(f"{level_id}: {level_available}/{level_total} available")


for spot_type, count in available_count.items():
print(f" {spot_type.value}: {count}")

total_spots += level_total
available_spots += level_available

print(f"Total: {available_spots}/{total_spots} spots available\n")


# Demo Usage
def demo_parking_lot():
# Create parking lot
parking_lot = ParkingLot("Downtown Parking", 2)

# Create vehicles
motorcycle = Vehicle("BIKE-123", VehicleType.MOTORCYCLE)
car1 = Vehicle("CAR-456", VehicleType.CAR)
car2 = Vehicle("CAR-789", VehicleType.CAR)
truck = Vehicle("TRUCK-101", VehicleType.TRUCK)

# Park vehicles
ticket1 = parking_lot.park_vehicle(motorcycle)
ticket2 = parking_lot.park_vehicle(car1)
ticket3 = parking_lot.park_vehicle(car2)
ticket4 = parking_lot.park_vehicle(truck)

# Check status
parking_lot.get_parking_status()

# Simulate some time passing and exit


import time
time.sleep(2) # Simulate 2 seconds (in real system, would be hours)

# Exit vehicles with different payment methods


if ticket1:
parking_lot.exit_vehicle(ticket1.ticket_id, CashPayment())

if ticket2:
parking_lot.exit_vehicle(ticket2.ticket_id, CardPayment("1234-5678-9012-3456"))

# Check status after exits


parking_lot.get_parking_status()

if __name__ == "__main__":
demo_parking_lot()

Memory Management and Python Internals


Understanding Python's low-level operations enhances your LLD skills[20][21].

Memory Allocation Patterns

import sys
import gc

class MemoryEfficientClass:
__slots__ = ['name', 'value', 'timestamp'] # Reduces memory usage

def __init__(self, name, value):


self.name = name
self.value = value
self.timestamp = datetime.now()
# Memory monitoring utility
class MemoryMonitor:
@staticmethod
def get_object_size(obj):
return sys.getsizeof(obj)

@staticmethod
def get_memory_info():
return {
'objects': len(gc.get_objects()),
'garbage': len(gc.garbage)
}

@staticmethod
def trigger_gc():
collected = gc.collect()
print(f"Garbage collected {collected} objects")

# Example: Memory-efficient data structures


class CircularBuffer:
def __init__(self, max_size):
self.max_size = max_size
self.buffer = [None] * max_size
self.head = 0
self.tail = 0
self.size = 0

def push(self, item):


if self.size < self.max_size:
self.buffer[self.tail] = item
self.tail = (self.tail + 1) % self.max_size
self.size += 1
else:
# Overwrite oldest item
self.buffer[self.tail] = item
self.tail = (self.tail + 1) % self.max_size
self.head = (self.head + 1) % self.max_size

def pop(self):
if self.size > 0:
item = self.buffer[self.head]
self.buffer[self.head] = None
self.head = (self.head + 1) % self.max_size
self.size -= 1
return item
return None

Test-Driven Development (TDD)


TDD ensures robust, maintainable code through a disciplined testing approach[22][23][24].
TDD Cycle Implementation

import unittest
from unittest.mock import Mock, patch

# Step 1: Write failing tests first


class TestBankAccount(unittest.TestCase):
def setUp(self):
self.account = BankAccount("ACC001", 1000)

def test_initial_balance(self):
self.assertEqual(self.account.get_balance(), 1000)

def test_deposit_positive_amount(self):
result = self.account.deposit(500)
self.assertTrue(result)
self.assertEqual(self.account.get_balance(), 1500)

def test_deposit_negative_amount(self):
result = self.account.deposit(-100)
self.assertFalse(result)
self.assertEqual(self.account.get_balance(), 1000)

def test_withdraw_valid_amount(self):
result = self.account.withdraw(300)
self.assertTrue(result)
self.assertEqual(self.account.get_balance(), 700)

def test_withdraw_insufficient_funds(self):
result = self.account.withdraw(1500)
self.assertFalse(result)
self.assertEqual(self.account.get_balance(), 1000)

def test_transaction_history(self):
self.account.deposit(200)
self.account.withdraw(100)
history = self.account._transaction_history
self.assertEqual(len(history), 2)
self.assertIn("Deposited: $200", history[0])
self.assertIn("Withdrew: $100", history[1])

# Step 2: Write minimal code to pass tests (already implemented above)

# Step 3: Refactor while keeping tests green


class TestPaymentProcessing(unittest.TestCase):
def setUp(self):
self.mock_payment_gateway = Mock()
self.payment_processor = PaymentProcessor(self.mock_payment_gateway)

def test_successful_payment(self):
self.mock_payment_gateway.charge.return_value = {"status": "success", "transactio

result = self.payment_processor.process_payment(100.0, "4111-1111-1111-1111")

self.assertTrue(result["success"])
self.assertEqual(result["transaction_id"], "TXN123")
self.mock_payment_gateway.charge.assert_called_once_with(100.0, "4111-1111-1111-1

def test_failed_payment(self):
self.mock_payment_gateway.charge.side_effect = Exception("Network error")

result = self.payment_processor.process_payment(100.0, "4111-1111-1111-1111")

self.assertFalse(result["success"])
self.assertIn("error", result)

# Advanced TDD: Integration testing


class TestParkingLotIntegration(unittest.TestCase):
def setUp(self):
self.parking_lot = ParkingLot("Test Parking", 1)
self.car = Vehicle("TEST-123", VehicleType.CAR)

def test_full_parking_cycle(self):
# Park vehicle
ticket = self.parking_lot.park_vehicle(self.car)
self.assertIsNotNone(ticket)
self.assertEqual(ticket.vehicle.license_plate, "TEST-123")

# Verify spot is occupied


spot = ticket.spot
self.assertEqual(spot.status, SpotStatus.OCCUPIED)
self.assertEqual(spot.vehicle, self.car)

# Exit vehicle
payment = CashPayment()
exit_success = self.parking_lot.exit_vehicle(ticket.ticket_id, payment)
self.assertTrue(exit_success)

# Verify spot is available


self.assertEqual(spot.status, SpotStatus.AVAILABLE)
self.assertIsNone(spot.vehicle)

if __name__ == '__main__':
unittest.main()

Hands-On Project 2: E-commerce Order Management System


This project demonstrates advanced LLD concepts with multiple design patterns[25][26].

from abc import ABC, abstractmethod


from enum import Enum
from decimal import Decimal
from typing import List, Dict, Optional
import uuid
from datetime import datetime

class OrderStatus(Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
PROCESSING = "processing"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"

class ProductCategory(Enum):
ELECTRONICS = "electronics"
CLOTHING = "clothing"
BOOKS = "books"
HOME = "home"

# Strategy Pattern for Pricing


class PricingStrategy(ABC):
@abstractmethod
def calculate_price(self, base_price: Decimal, quantity: int) -> Decimal:
pass

class RegularPricing(PricingStrategy):
def calculate_price(self, base_price: Decimal, quantity: int) -> Decimal:
return base_price * quantity

class BulkDiscountPricing(PricingStrategy):
def __init__(self, threshold: int, discount_percent: float):
self.threshold = threshold
self.discount_percent = discount_percent

def calculate_price(self, base_price: Decimal, quantity: int) -> Decimal:


total = base_price * quantity
if quantity >= self.threshold:
discount = total * Decimal(self.discount_percent / 100)
return total - discount
return total

class SeasonalPricing(PricingStrategy):
def __init__(self, discount_percent: float):
self.discount_percent = discount_percent

def calculate_price(self, base_price: Decimal, quantity: int) -> Decimal:


total = base_price * quantity
discount = total * Decimal(self.discount_percent / 100)
return total - discount

# Product and Inventory Management


class Product:
def __init__(self, product_id: str, name: str, category: ProductCategory,
base_price: Decimal, description: str = ""):
self.product_id = product_id
self.name = name
self.category = category
self.base_price = base_price
self.description = description
self.pricing_strategy = RegularPricing()

def set_pricing_strategy(self, strategy: PricingStrategy):


self.pricing_strategy = strategy

def calculate_price(self, quantity: int) -> Decimal:


return self.pricing_strategy.calculate_price(self.base_price, quantity)
class InventoryItem:
def __init__(self, product: Product, stock_quantity: int):
self.product = product
self.stock_quantity = stock_quantity
self.reserved_quantity = 0

def available_quantity(self) -> int:


return self.stock_quantity - self.reserved_quantity

def reserve_stock(self, quantity: int) -> bool:


if self.available_quantity() >= quantity:
self.reserved_quantity += quantity
return True
return False

def release_reservation(self, quantity: int):


self.reserved_quantity = max(0, self.reserved_quantity - quantity)

def fulfill_reservation(self, quantity: int):


if self.reserved_quantity >= quantity:
self.stock_quantity -= quantity
self.reserved_quantity -= quantity
return True
return False

# Observer Pattern for Order Status Updates


class OrderObserver(ABC):
@abstractmethod
def update(self, order_id: str, old_status: OrderStatus, new_status: OrderStatus):
pass

class EmailNotificationService(OrderObserver):
def update(self, order_id: str, old_status: OrderStatus, new_status: OrderStatus):
print(f"Email: Order {order_id} status changed from {old_status.value} to {new_st

class InventoryUpdateService(OrderObserver):
def __init__(self, inventory_manager):
self.inventory_manager = inventory_manager

def update(self, order_id: str, old_status: OrderStatus, new_status: OrderStatus):


if new_status == OrderStatus.CONFIRMED:
print(f"Inventory: Fulfilling reservations for order {order_id}")
elif new_status == OrderStatus.CANCELLED:
print(f"Inventory: Releasing reservations for order {order_id}")

# Order Management System


class OrderItem:
def __init__(self, product: Product, quantity: int):
self.product = product
self.quantity = quantity
self.unit_price = product.base_price
self.total_price = product.calculate_price(quantity)

class Order:
def __init__(self, customer_id: str):
self.order_id = str(uuid.uuid4())
self.customer_id = customer_id
self.items: List[OrderItem] = []
self.status = OrderStatus.PENDING
self.created_at = datetime.now()
self.total_amount = Decimal('0.00')
self._observers: List[OrderObserver] = []

def add_observer(self, observer: OrderObserver):


self._observers.append(observer)

def remove_observer(self, observer: OrderObserver):


if observer in self._observers:
self._observers.remove(observer)

def _notify_observers(self, old_status: OrderStatus):


for observer in self._observers:
observer.update(self.order_id, old_status, self.status)

def add_item(self, product: Product, quantity: int):


order_item = OrderItem(product, quantity)
self.items.append(order_item)
self._calculate_total()

def remove_item(self, product_id: str):


self.items = [item for item in self.items if item.product.product_id != product_i
self._calculate_total()

def _calculate_total(self):
self.total_amount = sum(item.total_price for item in self.items)

def update_status(self, new_status: OrderStatus):


old_status = self.status
self.status = new_status
self._notify_observers(old_status)

# State Pattern for Order Processing


class OrderState(ABC):
@abstractmethod
def confirm(self, order: Order) -> bool:
pass

@abstractmethod
def cancel(self, order: Order) -> bool:
pass

@abstractmethod
def ship(self, order: Order) -> bool:
pass

class PendingState(OrderState):
def confirm(self, order: Order) -> bool:
order.update_status(OrderStatus.CONFIRMED)
return True

def cancel(self, order: Order) -> bool:


order.update_status(OrderStatus.CANCELLED)
return True

def ship(self, order: Order) -> bool:


return False # Cannot ship pending order

class ConfirmedState(OrderState):
def confirm(self, order: Order) -> bool:
return False # Already confirmed

def cancel(self, order: Order) -> bool:


order.update_status(OrderStatus.CANCELLED)
return True

def ship(self, order: Order) -> bool:


order.update_status(OrderStatus.SHIPPED)
return True

# Command Pattern for Order Operations


class Command(ABC):
@abstractmethod
def execute(self):
pass

@abstractmethod
def undo(self):
pass

class AddItemCommand(Command):
def __init__(self, order: Order, product: Product, quantity: int):
self.order = order
self.product = product
self.quantity = quantity
self.executed = False

def execute(self):
if not self.executed:
self.order.add_item(self.product, self.quantity)
self.executed = True

def undo(self):
if self.executed:
self.order.remove_item(self.product.product_id)
self.executed = False

class OrderManager:
def __init__(self, inventory_manager):
self.orders: Dict[str, Order] = {}
self.inventory_manager = inventory_manager
self.email_service = EmailNotificationService()
self.inventory_service = InventoryUpdateService(inventory_manager)

def create_order(self, customer_id: str) -> Order:


order = Order(customer_id)
order.add_observer(self.email_service)
order.add_observer(self.inventory_service)
self.orders[order.order_id] = order
return order

def process_order(self, order_id: str) -> bool:


if order_id not in self.orders:
return False

order = self.orders[order_id]

# Reserve inventory
for item in order.items:
if not self.inventory_manager.reserve_stock(item.product.product_id, item.qua
print(f"Insufficient stock for {item.product.name}")
return False

# Confirm order
order.update_status(OrderStatus.CONFIRMED)
return True

class InventoryManager:
def __init__(self):
self.inventory: Dict[str, InventoryItem] = {}

def add_product(self, product: Product, quantity: int):


self.inventory[product.product_id] = InventoryItem(product, quantity)

def reserve_stock(self, product_id: str, quantity: int) -> bool:


if product_id in self.inventory:
return self.inventory[product_id].reserve_stock(quantity)
return False

def get_stock_level(self, product_id: str) -> int:


if product_id in self.inventory:
return self.inventory[product_id].available_quantity()
return 0

# Demo
def demo_ecommerce_system():
# Initialize system
inventory_manager = InventoryManager()
order_manager = OrderManager(inventory_manager)

# Create products with different pricing strategies


laptop = Product("LAPTOP001", "Gaming Laptop", ProductCategory.ELECTRONICS, Decimal("
laptop.set_pricing_strategy(BulkDiscountPricing(3, 10)) # 10% discount for 3+ items

book = Product("BOOK001", "Python Programming", ProductCategory.BOOKS, Decimal("45.00


book.set_pricing_strategy(SeasonalPricing(15)) # 15% seasonal discount

# Add to inventory
inventory_manager.add_product(laptop, 10)
inventory_manager.add_product(book, 50)

# Create and process order


order = order_manager.create_order("CUSTOMER001")
# Add items using command pattern
add_laptop_cmd = AddItemCommand(order, laptop, 2)
add_book_cmd = AddItemCommand(order, book, 3)

add_laptop_cmd.execute()
add_book_cmd.execute()

print(f"Order total: ${order.total_amount}")


print(f"Laptop stock before: {inventory_manager.get_stock_level('LAPTOP001')}")

# Process order
success = order_manager.process_order(order.order_id)
print(f"Order processed: {success}")

print(f"Laptop stock after: {inventory_manager.get_stock_level('LAPTOP001')}")

if __name__ == "__main__":
demo_ecommerce_system()

Advanced Topics and Best Practices

Performance Considerations

# Use generators for memory efficiency


def generate_large_dataset():
for i in range(1000000):
yield {"id": i, "value": i * 2}

# Lazy loading pattern


class LazyLoader:
def __init__(self, loader_func):
self._loader_func = loader_func
self._data = None

@property
def data(self):
if self._data is None:
self._data = self._loader_func()
return self._data

# Connection pooling
class ConnectionPool:
def __init__(self, max_connections=10):
self.max_connections = max_connections
self._connections = []
self._in_use = set()

def get_connection(self):
if self._connections:
conn = self._connections.pop()
elif len(self._in_use) < self.max_connections:
conn = self._create_connection()
else:
raise Exception("No available connections")
self._in_use.add(conn)
return conn

def return_connection(self, conn):


if conn in self._in_use:
self._in_use.remove(conn)
self._connections.append(conn)

def _create_connection(self):
return f"Connection-{len(self._in_use) + len(self._connections)}"

Error Handling and Logging

import logging
from functools import wraps

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Decorator for logging


def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
logger.info(f"{func.__name__} returned {result}")
return result
except Exception as e:
logger.error(f"{func.__name__} raised {type(e).__name__}: {e}")
raise
return wrapper

# Custom exceptions
class InsufficientFundsError(Exception):
def __init__(self, requested_amount, available_amount):
self.requested_amount = requested_amount
self.available_amount = available_amount
super().__init__(f"Insufficient funds: requested {requested_amount}, available {a

class InvalidProductError(Exception):
pass

# Result pattern for error handling


class Result:
def __init__(self, success=True, data=None, error=None):
self.success = success
self.data = data
self.error = error

@classmethod
def ok(cls, data):
return cls(success=True, data=data)
@classmethod
def error(cls, error):
return cls(success=False, error=error)

Learning Path and Next Steps

Beginner to Intermediate (Months 1-3)


1. Master OOP fundamentals: Classes, inheritance, polymorphism, encapsulation
2. Learn SOLID principles: Practice with small projects
3. Implement basic patterns: Factory, Singleton, Observer
4. Practice TDD: Write tests first, then implementation

Intermediate to Advanced (Months 4-6)


1. Complex design patterns: Strategy, Command, State, Decorator
2. System design projects: Build parking lot, library management, chat system
3. Performance optimization: Memory management, algorithm efficiency
4. Integration patterns: Database connections, external APIs

Advanced Mastery (Months 7-12)


1. Architecture patterns: MVC, MVP, Clean Architecture
2. Distributed systems concepts: Microservices, event-driven architecture
3. Advanced testing: Integration tests, performance tests, contract tests
4. Production considerations: Monitoring, logging, error handling

Recommended Practice Problems


1. Design a parking lot system (covered above)[17][19]
2. Design a library management system[27]
3. Design an elevator system[28]
4. Design a chat system[29]
5. Design a chess game[28]
6. Design a ride-sharing system[2]

Resources for Continued Learning


Books: "Head First Design Patterns"[30], "Clean Code"[31], "Architecture Patterns with
Python"[32]
GitHub Repositories: Practice problems and solutions[33][34][29]
Online Courses: System design interviews[31], Python design patterns[15]
Code Practice Platforms: LeetCode system design, CodeZym LLD problems[28]

Conclusion
Low Level Design programming in Python requires mastering object-oriented principles, design
patterns, and system architecture concepts. Through hands-on projects like the parking lot and
e-commerce systems demonstrated above, you build practical skills in creating maintainable,
scalable, and robust applications.
The key to excellence in LLD is consistent practice with real-world projects, understanding the
trade-offs between different design choices, and continuously refining your approach based on
requirements and constraints. Remember that good design is not about applying every pattern
possible, but about choosing the right tools for the specific problem at hand[35].
Start with the fundamentals, practice regularly with coding projects, and gradually tackle more
complex system design challenges. The combination of theoretical knowledge and practical
implementation will prepare you for both technical interviews and real-world software
development challenges.

You might also like