Software Engineering

Designing, building, and maintaining software systems.

1. Design Pattern โ€” Singleton (Thread-Safe)

1. Design Pattern โ€” Singleton (Thread-Safe) is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Design principles, analysis methods, standards and codes, and real-world case studies.

2. Design Pattern โ€” Observer Pattern

2. Design Pattern โ€” Observer Pattern is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Design principles, analysis methods, standards and codes, and real-world case studies.

3. RESTful Service with Clean Architecture

3. RESTful Service with Clean Architecture is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

4. Unit Testing with pytest

4. Unit Testing with pytest is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

5. CI/CD Pipeline Configuration (GitHub Actions)

5. CI/CD Pipeline Configuration (GitHub Actions) is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

6. Git Workflow & Branching Strategy

6. Git Workflow & Branching Strategy is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

7. Code Review Checklist Generator

7. Code Review Checklist Generator is a core technology topic with practical implementations, complexity analysis, and real-world applications.

Key points: Syntax and semantics, best practices, debugging techniques, and project-based application.

8. Database Migration with Alembic

8. Database Migration with Alembic is a core technology topic with practical implementations, complexity analysis, and real-world applications.

Key points: Syntax and semantics, best practices, debugging techniques, and project-based application.

9. API Versioning & Documentation

9. API Versioning & Documentation is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Syntax and semantics, best practices, debugging techniques, and project-based application.

10. Performance Profiling & Monitoring

10. Performance Profiling & Monitoring is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

Software Development Life Cycle (SDLC)

Software Development Life Cycle (SDLC) is a core technology topic with practical implementations, complexity analysis, and real-world applications.

Key points: Syntax and semantics, best practices, debugging techniques, and project-based application.

Agile & Scrum Methodology

Agile & Scrum Methodology is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

SOLID Principles

SOLID Principles is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

Design Patterns (GoF)

Design Patterns (GoF) is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Design principles, analysis methods, standards and codes, and real-world case studies.

Testing Pyramid

Testing Pyramid is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

Version Control Best Practices

Version Control Best Practices is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

Microservices Architecture

Microservices Architecture is a fundamental engineering principle covering design, analysis, and practical application.

Key points: Core principles, practical applications, and advanced concepts that build deep understanding.

Clean Code Principles

Clean Code Principles is a core technology topic with practical implementations, complexity analysis, and real-world applications.

Key points: Syntax and semantics, best practices, debugging techniques, and project-based application.

Key Code Examples

1. Design Pattern โ€” Singleton (Thread-Safe)

import threading from functools import wraps def singleton(cls): """Thread-safe singleton decorator.""" instances = {} lock = threading.Lock() @wraps(cls) def get_instance(*args, **kwargs): if cls not in instances: with lock: # Double-checked locking if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class DatabaseConnection: """Singleton database connection manager.""" def __init__(self, connection_string="postgresql://localhost/app"): self.connection_string = connection_string self._connection = None self._lock = threading.Lock() print(f"Creating DB connection to {connection_string}") def execute(self, query): with self._lock: print(f"Executing: {query}") return [{"id": 1, "name": "result"}] def close(self): if self._connection: self._connection.close() self._connection = None # Usage db1 = DatabaseConnection() # Creates instance db2 = DatabaseConnection() # Returns same instance print(db1 is db2) # True

2. Design Pattern โ€” Observer Pattern

from typing import List, Callable from abc import ABC, abstractmethod class Observer(ABC): @abstractmethod def update(self, event: str, data: dict): pass class Subject: """Observable subject that notifies observers of changes.""" def __init__(self): self._observers: List[Observer] = [] self._state = {} def attach(self, observer: Observer): if observer not in self._observers: self._observers.append(observer) def detach(self, observer: Observer): self._observers.remove(observer) def notify(self, event: str, data: dict = None): for observer in self._observers: observer.update(event, data or {}) def set_state(self, key: str, value): self._state[key] = value self.notify("state_changed", {"key": key, "value": value}) # Concrete implementations class EmailNotifier(Observer): def update(self, event: str, data: dict): print(f"[EMAIL] Event: {event}, Data: {data}") class SMSNotifier(Observer): def update(self, event: str, data: dict): print(f"[SMS] Event: {event}, Data: {data}") class Logger(Observer): def update(self, event: str, data: dict): print(f"[LOG] {event}: {data}") # Usage order_service = Subject() order_service.attach(EmailNotifier()) order_service.attach(Logger()) order_service.set_state("order_status", "shipped") # [EMAIL] Event: state_changed, Data: {'key': 'order_status', 'value': 'shipped'} # [LOG] state_changed: {'key': 'order_status', 'value': 'shipped'}

3. RESTful Service with Clean Architecture

from abc import ABC, abstractmethod from dataclasses import dataclass from typing import List, Optional import uuid from datetime import datetime # ===== Domain Layer ===== @dataclass class User: id: str email: str name: str created_at: datetime class UserRepository(ABC): """Abstract repository โ€” domain layer defines interface.""" @abstractmethod def find_by_id(self, user_id: str) -> Optional[User]: pass @abstractmethod def find_by_email(self, email: str) -> Optional[User]: pass @abstractmethod def save(self, user: User) -> User: pass @abstractmethod def delete(self, user_id: str) -> bool: pass @abstractmethod def list_all(self, page: int = 1, limit: int = 20) -> List[User]: pass # ===== Application Layer (Use Cases) ===== class UserService: """Business logic lives here โ€” independent of frameworks.""" def __init__(self, repository: UserRepository): self.repository = repository def register_user(self, email: str, name: str) -> User: # Business validation if not email or '@' not in email: raise ValueError("Invalid email address") # Check for existing user existing = self.repository.find_by_email(email) if existing: raise ValueError(f"User with email {email} already exists") # Create user user = User( id=str(uuid.uuid4()), email=email, name=name, created_at=datetime.utcnow() ) return self.repository.save(user) def get_user_profile(self, user_id: str) -> dict: user = self.repository.find_by_id(user_id) if not user: raise NotFoundError(f"User {user_id} not found") return { "id": user.id, "email": user.email, "name": user.name, "member_since": user.created_at.strftime("%Y-%m-%d") } def deactivate_user(self, user_id: str) -> bool: # Business rule: can't deactivate admin users user = self.repository.find_by_id(user_id) if not user: raise NotFoundError(f"User {user_id} not found") return self.repository.delete(user_id) # ===== Infrastructure Layer ===== class InMemoryUserRepository(UserRepository): """In-memory implementation for testing.""" def __init__(self): self._users = {} def find_by_id(self, user_id: str) -> Optional[User]: return self._users.get(user_id) def find_by_email(self, email: str) -> Optional[User]: for user in self._users.values(): if user.email == email: return user return None def save(self, user: User) -> User: self._users[user.id] = user return user def delete(self, user_id: str) -> bool: if user_id in self._users: del self._users[user_id] return True return False def list_all(self, page=1, limit=20): start = (page - 1) * limit end = start + limit return list(self._users.values())[start:end] class NotFoundError(Exception): pass # ===== Dependency Injection ===== # Composition root โ€” wire everything together repository = InMemoryUserRepository() user_service = UserService(repository)

4. Unit Testing with pytest

import pytest from unittest.mock import Mock, patch from datetime import datetime class TestUserService: """Unit tests for UserService with mocked dependencies.""" def setup_method(self): """Set up test fixtures.""" self.mock_repo = Mock() self.service = UserService(self.mock_repo) def test_register_user_success(self): """Test successful user registration.""" # Arrange self.mock_repo.find_by_email.return_value = None self.mock_repo.save.side_effect = lambda u: u # Act user = self.service.register_user("alice@example.com", "Alice") # Assert assert user.email == "alice@example.com" assert user.name == "Alice" assert user.id is not None self.mock_repo.save.assert_called_once() def test_register_duplicate_email(self): """Test registration fails for duplicate email.""" # Arrange existing = User(id="123", email="alice@example.com", name="Existing", created_at=datetime.now()) self.mock_repo.find_by_email.return_value = existing # Act & Assert with pytest.raises(ValueError, match="already exists"): self.service.register_user("alice@example.com", "Alice") self.mock_repo.save.assert_not_called() def test_register_invalid_email(self): """Test registration fails for invalid email.""" with pytest.raises(ValueError, match="Invalid email"): self.service.register_user("not-an-email", "Alice") def test_get_user_profile_success(self): """Test retrieving user profile.""" user = User(id="42", email="bob@example.com", name="Bob", created_at=datetime(2026, 1, 15, 10, 30)) self.mock_repo.find_by_id.return_value = user profile = self.service.get_user_profile("42") assert profile["id"] == "42" assert profile["email"] == "bob@example.com" assert profile["name"] == "Bob" assert profile["member_since"] == "2026-01-15" def test_get_user_profile_not_found(self): """Test profile retrieval for non-existent user.""" self.mock_repo.find_by_id.return_value = None with pytest.raises(NotFoundError): self.service.get_user_profile("nonexistent") @patch('builtins.print') def test_service_prints_confirmation(self, mock_print): """Test that service logs confirmation (with mocked print).""" # ... test logic with mocked side effects pass # Run: pytest test_user_service.py -v --tb=short

5. CI/CD Pipeline Configuration (GitHub Actions)

# .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '20' PYTHON_VERSION: '3.11' jobs: # ===== Stage 1: Code Quality ===== lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - run: npm ci - run: npm run lint - run: npm run type-check # ===== Stage 2: Testing ===== test: runs-on: ubuntu-latest needs: lint strategy: matrix: python-version: ['3.10', '3.11', '3.12'] services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: test_password ports: ['5432:5432'] options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-cov - name: Run tests with coverage env: DATABASE_URL: postgresql://postgres:test_password@localhost:5432/test_db run: | pytest --cov=src --cov-report=xml --cov-report=html - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.xml - name: Enforce coverage threshold run: | coverage report --fail-under=80 # ===== Stage 3: Security Scan ===== security: runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Run Bandit (Python security) run: | pip install bandit bandit -r src/ -f json -o bandit-report.json || true - name: Upload security results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' # ===== Stage 4: Build & Deploy ===== deploy: runs-on: ubuntu-latest needs: [test, security] if: github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build and push Docker image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ github.sha }} run: | docker build -t $ECR_REGISTRY/eduverse-api:$IMAGE_TAG . docker push $ECR_REGISTRY/eduverse-api:$IMAGE_TAG - name: Deploy to ECS run: | aws ecs update-service \ --cluster eduverse-production \ --service api-service \ --force-new-deployment - name: Smoke tests run: | sleep 30 curl -f https://api.eduverse.com/health || exit 1

6. Git Workflow & Branching Strategy

# GitFlow branching strategy implementation # ===== Initialize GitFlow ===== git flow init -d # Uses default branch names # ===== Feature Development ===== git flow feature start user-authentication # Creates feature/user-authentication from develop # Work on feature... git add . git commit -m "feat(auth): add JWT token generation" git commit -m "feat(auth): implement refresh token rotation" git commit -m "test(auth): add unit tests for token service" git flow feature finish user-authentication # Merges into develop, deletes feature branch # ===== Release Process ===== git flow release start v2.1.0 # Creates release/v2.1.0 from develop # Version bump, final fixes echo "2.1.0" > VERSION git add VERSION git commit -m "chore(release): bump version to 2.1.0" git flow release finish v2.1.0 # Merges into main and develop, creates tag v2.1.0 # Push everything git push origin main develop --tags # ===== Hotfix Process ===== git flow hotfix start v2.1.1 # Creates hotfix/v2.1.1 from main # Fix the critical bug git commit -m "fix(api): resolve null pointer in user endpoint" git flow hotfix finish v2.1.1 # Merges into main AND develop, creates tag # ===== Useful Aliases ===== git config --global alias.lg "log --graph --oneline --decorate --all" git config --global alias.st "status -sb" git config --global alias.co "checkout" git config --global alias.br "branch -vv" git config --global alias.ci "commit" git config --global alias.unstage "reset HEAD --" git config --global alias.last "log -1 HEAD"

7. Code Review Checklist Generator

from dataclasses import dataclass, field from typing import List, Dict from enum import Enum import re class Severity(Enum): CRITICAL = "CRITICAL" WARNING = "WARNING" SUGGESTION = "SUGGESTION" @dataclass class ReviewFinding: category: str severity: Severity message: str line: int = 0 suggestion: str = "" class CodeReviewer: """Automated code review assistant.""" CHECKS = { "security": [ (r'print\(.*password', Severity.CRITICAL, "Possible password in log output", "Use logging with redaction"), (r'except\s*:', Severity.WARNING, "Bare except clause catches all exceptions", "Use 'except Exception:' or specific exceptions"), (r'eval\(', Severity.CRITICAL, "Use of eval() is dangerous", "Use ast.literal_eval() or json.loads()"), (r'subprocess\..*shell\s*=\s*True', Severity.WARNING, "Shell=True can lead to command injection", "Use shell=False with list arguments"), ], "quality": [ (r'def\s+\w+\([^)]*\)\s*->\s*None.*\n(?:.*\n){50,}', Severity.SUGGESTION, "Very long function (>50 lines)", "Consider breaking into smaller functions"), (r'# TODO|# FIXME|# HACK', Severity.WARNING, "Unresolved TODO/FIXME comment", "Address or create a ticket"), (r'print\(', Severity.SUGGESTION, "Using print() for logging", "Use the logging module instead"), ], "performance": [ (r'for\s+\w+\s+in\s+.*:\s*\n\s+for\s+\w+\s+in', Severity.WARNING, "Nested loops may have O(nยฒ) complexity", "Consider optimization"), (r'\.append\(.*\).*\n.*\.append\(', Severity.SUGGESTION, "Repeated append in loop", "Consider list comprehension"), ] } def review_file(self, filepath: str, content: str) -> List[ReviewFinding]: """Review a single file for issues.""" findings = [] lines = content.split('\n') for category, rules in self.CHECKS.items(): for pattern, severity, message, suggestion in rules: for match in re.finditer(pattern, content, re.MULTILINE): line_num = content[:match.start()].count('\n') + 1 findings.append(ReviewFinding( category=category, severity=severity, message=message, line=line_num, suggestion=suggestion )) return findings def generate_report(self, findings: List[ReviewFinding]) -> str: """Generate a human-readable review report.""" report = ["# Code Review Report\n"] for severity in [Severity.CRITICAL, Severity.WARNING, Severity.SUGGESTION]: items = [f for f in findings if f.severity == severity] if items: report.append(f"## {severity.value} ({len(items)} issues)\n") for item in items: report.append(f" Line {item.line}: {item.message}") report.append(f" Category: {item.category}") if item.suggestion: report.append(f" Suggestion: {item.suggestion}") report.append("") return "\n".join(report) # Usage reviewer = CodeReviewer() with open('app.py', 'r') as f: findings = reviewer.review_file('app.py', f.read()) print(reviewer.generate_report(findings))

8. Database Migration with Alembic

# alembic migration example # alembic/versions/001_create_users_table.py """Create users table Revision ID: 001 Revises: Create Date: 2026-01-15 10:00:00.000000 """ from alembic import op import sqlalchemy as sa # revision identifiers revision = '001' down_revision = None branch_labels = None depends_on = None def upgrade(): op.create_table( 'users', sa.Column('id', sa.BigInteger(), primary_key=True, autoincrement=True), sa.Column('email', sa.String(255), unique=True, nullable=False), sa.Column('password_hash', sa.String(255), nullable=False), sa.Column('first_name', sa.String(100), nullable=False), sa.Column('last_name', sa.String(100), nullable=False), sa.Column('is_active', sa.Boolean(), server_default='true'), sa.Column('role', sa.String(20), server_default='customer'), sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()), sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now()), ) op.create_index('idx_users_email', 'users', ['email']) op.create_index('idx_users_role', 'users', ['role']) def downgrade(): op.drop_index('idx_users_role', table_name='users') op.drop_index('idx_users_email', table_name='users') op.drop_table('users') # Zero-downtime migration strategy for large tables: # 1. Add new column (nullable) # 2. Backfill in batches # 3. Deploy code that writes to both old and new columns # 4. Deploy code that reads from new column # 5. Drop old column # # commands: # alembic revision --autogenerate -m "Add user preferences" # alembic upgrade head # alembic downgrade -1 # alembic current # alembic history

9. API Versioning & Documentation

from functools import wraps from flask import Flask, request, jsonify app = Flask(__name__) class APIVersionManager: """Manage multiple API versions with deprecation support.""" SUPPORTED_VERSIONS = ['v1', 'v2'] DEFAULT_VERSION = 'v2' DEPRECATED_VERSIONS = ['v1'] DEPRECATION_DATE = '2026-06-01' SUNSET_DATE = '2026-12-01' def versioned_route(self, rule, version=None, **options): """Decorator for versioned API routes.""" def decorator(f): endpoint = options.pop('endpoint', None) # Register for default version if version is None or version == self.DEFAULT_VERSION: app.add_url_rule(rule, endpoint, f, **options) # Register version-specific versioned_rule = f"/api/{version or self.DEFAULT_VERSION}{rule}" app.add_url_rule(versioned_rule, f"{endpoint}_{version}", f, **options) # Add deprecation headers @wraps(f) def wrapped(*args, **kwargs): response = f(*args, **kwargs) if isinstance(response, tuple): resp, status = response if version in self.DEPRECATED_VERSIONS: resp = jsonify(resp) resp.headers['Deprecation'] = 'true' resp.headers['Sunset'] = self.SUNSET_DATE resp.headers['Link'] = f'; rel="successor-version"' return resp, status return response return wrapped return decorator # Usage api = APIVersionManager() @app.route('/api/v1/users', methods=['GET']) def get_users_v1(): """Original v1 endpoint.""" return jsonify([{"id": 1, "name": "Alice"}]) @app.route('/api/v2/users', methods=['GET']) def get_users_v2(): """Enhanced v2 endpoint.""" return jsonify({ "data": [{"id": 1, "name": "Alice", "email": "alice@example.com"}], "meta": {"page": 1, "total": 100, "version": "v2"} }) # OpenAPI/Swagger documentation (using flasgger) from flasgger import Swagger swagger = Swagger(app, template={ "info": { "title": "EduVerse API", "version": "2.0.0", "description": "API for EduVerse Platform" }, "basePath": "/api/v2", "schemes": ["https"] }) @app.route('/api/v2/products', methods=['GET']) def list_products(): """ List all products --- tags: - Products parameters: - name: page in: query type: integer default: 1 - name: category in: query type: string responses: 200: description: List of products schema: type: object properties: data: type: array items: type: object """ return jsonify({"data": [], "meta": {}})

10. Performance Profiling & Monitoring

import time import tracemalloc from functools import wraps from collections import defaultdict import statistics class PerformanceMonitor: """Application performance monitoring.""" def __init__(self): self.metrics = defaultdict(list) self.errors = defaultdict(int) def track_time(self, operation_name): """Decorator to track function execution time.""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() try: result = func(*args, **kwargs) elapsed = time.perf_counter() - start self.metrics[operation_name].append(elapsed) return result except Exception as e: self.errors[f"{operation_name}_error"] += 1 raise return wrapper return decorator def track_memory(self, func): """Decorator to track memory usage.""" @wraps(func) def wrapper(*args, **kwargs): tracemalloc.start() result = func(*args, **kwargs) current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() self.metrics[f"{func.__name__}_memory_peak"].append(peak) return result return wrapper def get_stats(self, operation_name: str = None) -> dict: """Get performance statistics.""" if operation_name: times = self.metrics[operation_name] if not times: return {} return { "operation": operation_name, "count": len(times), "total_time": sum(times), "mean": statistics.mean(times), "median": statistics.median(times), "p95": sorted(times)[int(len(times) * 0.95)], "p99": sorted(times)[int(len(times) * 0.99)], "min": min(times), "max": max(times), } return {name: self.get_stats(name) for name in self.metrics} def report(self) -> str: """Generate a performance report.""" lines = ["=== Performance Report ===\n"] for name in sorted(self.metrics.keys()): stats = self.get_stats(name) lines.append(f"{name}:") lines.append(f" Calls: {stats['count']}") lines.append(f" Mean: {stats['mean']*1000:.2f}ms") lines.append(f" P95: {stats['p95']*1000:.2f}ms") lines.append(f" P99: {stats['p99']*1000:.2f}ms") lines.append(f" Max: {stats['max']*1000:.2f}ms") lines.append("") if self.errors: lines.append("Errors:") for error, count in self.errors.items(): lines.append(f" {error}: {count}") return "\n".join(lines) # Usage monitor = PerformanceMonitor() @monitor.track_time("db_query") def get_users_from_db(): time.sleep(0.05) # Simulate DB query return [{"id": 1}] @monitor.track_time("api_call") def call_external_api(): time.sleep(0.1) # Simulate API call return {"status": "ok"} # Run and report for _ in range(100): get_users_from_db() call_external_api() print(monitor.report())

Core Concepts

Software Development Life Cycle (SDLC)

Definition: A structured process for planning, creating, testing, and deploying software.

  • Requirements: Gather and document what the software needs to do
  • Design: Create architecture, database schema, UI/UX designs
  • Implementation: Write code following design specifications
  • Testing: Verify correctness (unit, integration, system, acceptance)
  • Deployment: Release to production environment
  • Maintenance: Fix bugs, add features, optimize performance

Agile & Scrum Methodology

Agile: Iterative development with frequent delivery and feedback.

Scrum Framework:

  • Sprint: 1-4 week time-boxed iteration
  • Product Backlog: Prioritized list of features
  • Sprint Backlog: Items selected for current sprint
  • Daily Standup: 15-min daily sync meeting
  • Sprint Review: Demo completed work to stakeholders
  • Retrospective: Reflect on what went well/improve
  • Key Roles: Product Owner, Scrum Master, Development Team

SOLID Principles

Single Responsibility: A class should have only one reason to change (one job).

Open/Closed: Open for extension, closed for modification (use interfaces).

Liskov Substitution: Subtypes must be substitutable for their base types.

Interface Segregation: Many small interfaces beat one large interface.

Dependency Inversion: Depend on abstractions, not concrete implementations.

Design Patterns (GoF)

Creational: How objects are created

  • Singleton: One instance of a class (database connection)
  • Factory: Create objects without specifying exact class
  • Builder: Construct complex objects step by step

Structural: How classes/objects are composed

  • Adapter: Make incompatible interfaces work together
  • Decorator: Add behavior dynamically
  • Facade: Simplified interface to complex subsystem

Behavioral: How objects interact

  • Observer: Notify dependents of state changes
  • Strategy: Interchangeable algorithms
  • Command: Encapsulate request as object

Testing Pyramid

Unit Tests (70%): Test individual functions/components in isolation. Fast, reliable.

Integration Tests (20%): Test how modules work together. DB, API, services.

E2E Tests (10%): Test complete user workflows from UI to database. Slow but comprehensive.

Key Practices: TDD (Test-Driven Development), mocking, CI/CD integration, coverage targets

Version Control Best Practices

  • Commit Often: Small, logical commits with descriptive messages
  • Branching: GitFlow, GitHub Flow, or trunk-based development
  • Code Review: All code reviewed by at least one peer
  • Never Commit Secrets: Use .gitignore, environment variables
  • Conventional Commits: feat:, fix:, chore:, docs:, test:

Microservices Architecture

Definition: Decomposing an application into small, independently deployable services.

Benefits: Independent scaling, technology diversity, fault isolation, faster deployment.

Challenges: Distributed system complexity, data consistency, service discovery, monitoring.

Key Patterns: API Gateway, Circuit Breaker, Event Sourcing, CQRS, Saga Pattern.

Clean Code Principles

  • Meaningful Names: Variables, functions, classes should clearly describe their purpose
  • Small Functions: Under 20 lines, single responsibility
  • No Comments Needed: Code should be self-documenting
  • DRY (Don't Repeat Yourself): Extract shared logic
  • Error Handling: Use exceptions, not error codes
  • Testing: Every function should have corresponding tests

Worked Examples

Example 1: Refactoring Legacy Code

Problem: Refactor this spaghetti code into clean, maintainable code.

Before:

def process_order(data): # 200 lines of mixed concerns... # validate, calculate, save, send email, update inventory # all in one giant function if data['type'] == 'standard': price = data['price'] * 1.1 # tax elif data['type'] == 'express': price = data['price'] * 1.2 elif data['type'] == 'international': price = data['price'] * 1.3 # ... 190 more lines

After (Clean Code):

class OrderProcessor: def __init__(self, tax_calculator, notifier, inventory): self.tax_calculator = tax_calculator self.notifier = notifier self.inventory = inventory def process(self, order_data: dict) -> Order: order = self._create_order(order_data) order = self._apply_pricing(order) self._save_order(order) self._notify_customer(order) self._update_inventory(order) return order def _create_order(self, data: dict) -> Order: return Order( id=str(uuid.uuid4()), items=data['items'], customer_email=data['email'], shipping_address=data['address'], created_at=datetime.utcnow() ) def _apply_pricing(self, order: Order) -> Order: order.subtotal = sum(item.price * item.qty for item in order.items) order.tax = self.tax_calculator.calculate(order) order.total = order.subtotal + order.tax return order def _save_order(self, order: Order): self.repository.save(order) def _notify_customer(self, order: Order): self.notifier.send_order_confirmation(order) def _update_inventory(self, order: Order): for item in order.items: self.inventory.decrease_stock(item.product_id, item.qty)

Example 2: Implement TDD Workflow

Problem: Implement a shopping cart using Test-Driven Development.

Step 1: Write failing test first

def test_add_item_to_cart(): cart = ShoppingCart() cart.add_item("PROD-001", quantity=2, price=29.99) assert cart.item_count == 1 assert cart.total == 59.98

Step 2: Write minimal implementation

class ShoppingCart: def __init__(self): self._items = [] self.item_count = 0 self.total = 0.0 def add_item(self, product_id, quantity, price): self._items.append({"id": product_id, "qty": quantity, "price": price}) self.item_count += 1 self.total += quantity * price

Step 3: Refactor and add more tests

def test_apply_discount(): cart = ShoppingCart() cart.add_item("PROD-001", quantity=1, price=100.0) cart.apply_discount(code="SAVE10", percent=10) assert cart.total == 90.0 def test_remove_item(): cart = ShoppingCart() cart.add_item("PROD-001", quantity=2, price=10.0) cart.remove_item("PROD-001") assert cart.item_count == 0 assert cart.total == 0.0 # Continue cycle: Red โ†’ Green โ†’ Refactor

Example 3: Database Migration Zero-Downtime

Problem: Add a NOT NULL column `phone_number` to a 10M-row users table without downtime.

Step-by-step migration:

-- Migration 1: Add nullable column (fast, no table rebuild) ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) NULL; -- Completes in < 1 second (metadata only in MySQL 8+) -- Migration 2: Backfill in batches (run over hours/days) UPDATE users SET phone_number = '+10000000000' WHERE id BETWEEN 1 AND 100000 AND phone_number IS NULL; -- Repeat for each batch of 100K rows... -- Migration 3: Deploy code that writes phone_number -- Application now includes phone_number in INSERT/UPDATE -- Migration 4: Verify backfill complete SELECT COUNT(*) FROM users WHERE phone_number IS NULL; -- Should be 0 -- Migration 5: Add NOT NULL constraint ALTER TABLE users MODIFY COLUMN phone_number VARCHAR(20) NOT NULL; -- Fast because no NULL values exist

Example 4: Circuit Breaker Pattern

Problem: Implement a circuit breaker to prevent cascading failures when calling an unreliable external service.

import time import threading from enum import Enum class CircuitState(Enum): CLOSED = "CLOSED" # Normal operation OPEN = "OPEN" # Failing, reject requests HALF_OPEN = "HALF_OPEN" # Testing if service recovered class CircuitBreaker: def __init__(self, failure_threshold=5, recovery_timeout=30, half_open_max_calls=3): self.failure_threshold = failure_threshold self.recovery_timeout = recovery_timeout self.half_open_max_calls = half_open_max_calls self.state = CircuitState.CLOSED self.failure_count = 0 self.last_failure_time = None self.half_open_calls = 0 self.lock = threading.Lock() def call(self, func, *args, **kwargs): with self.lock: if self.state == CircuitState.OPEN: if self._should_attempt_reset(): self.state = CircuitState.HALF_OPEN self.half_open_calls = 0 else: raise CircuitBreakerOpenError( "Service unavailable โ€” circuit breaker is OPEN" ) if self.state == CircuitState.HALF_OPEN: if self.half_open_calls >= self.half_open_max_calls: raise CircuitBreakerOpenError( "Half-open call limit reached" ) self.half_open_calls += 1 try: result = func(*args, **kwargs) self._on_success() return result except Exception as e: self._on_failure() raise def _on_success(self): with self.lock: if self.state == CircuitState.HALF_OPEN: self.state = CircuitState.CLOSED self.failure_count = 0 def _on_failure(self): with self.lock: self.failure_count += 1 self.last_failure_time = time.time() if self.failure_count >= self.failure_threshold: self.state = CircuitState.OPEN def _should_attempt_reset(self): return (time.time() - self.last_failure_time) >= self.recovery_timeout class CircuitBreakerOpenError(Exception): pass # Usage breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=60) try: result = breaker.call(unreliable_api_call, "param") except CircuitBreakerOpenError: # Return cached data or default response result = get_cached_data()

Example 5: Implement Repository Pattern

Problem: Abstract data access to make the application testable and database-agnostic.

from abc import ABC, abstractmethod from typing import List, Optional # Domain model @dataclass class Product: id: int name: str price: float stock: int # Abstract repository class ProductRepository(ABC): @abstractmethod def get_by_id(self, product_id: int) -> Optional[Product]: pass @abstractmethod def search(self, query: str) -> List[Product]: pass @abstractmethod def save(self, product: Product) -> Product: pass @abstractmethod def update_stock(self, product_id: int, quantity: int) -> bool: pass # SQL implementation class SQLProductRepository(ProductRepository): def __init__(self, db_connection): self.db = db_connection def get_by_id(self, product_id: int) -> Optional[Product]: row = self.db.execute( "SELECT id, name, price, stock FROM products WHERE id = %s", (product_id,) ).fetchone() return Product(**row) if row else None def search(self, query: str) -> List[Product]: rows = self.db.execute( "SELECT id, name, price, stock FROM products WHERE name ILIKE %s", (f'%{query}%',) ).fetchall() return [Product(**row) for row in rows] # Cache decorator class CachedProductRepository(ProductRepository): def __init__(self, repository: ProductRepository, ttl=300): self.repository = repository self.cache = {} self.ttl = ttl def get_by_id(self, product_id: int) -> Optional[Product]: key = f"product:{product_id}" if key in self.cache: cached, timestamp = self.cache[key] if time.time() - timestamp < self.ttl: return cached product = self.repository.get_by_id(product_id) if product: self.cache[key] = (product, time.time()) return product

Practice Problems

Problem 1: Implement Strategy Pattern

Create a payment processing system using the Strategy pattern for different payment methods.

from abc import ABC, abstractmethod class PaymentStrategy(ABC): @abstractmethod def pay(self, amount: float) -> dict: pass class CreditCardPayment(PaymentStrategy): def __init__(self, card_number, cvv, expiry): self.card_number = card_number[-4:] # Only store last 4 digits def pay(self, amount: float) -> dict: # Process credit card payment return {"status": "success", "method": "credit_card", "amount": amount, "last4": self.card_number} class PayPalPayment(PaymentStrategy): def __init__(self, email): self.email = email def pay(self, amount: float) -> dict: return {"status": "success", "method": "paypal", "amount": amount, "email": self.email} class CryptoPayment(PaymentStrategy): def __init__(self, wallet_address): self.wallet_address = wallet_address def pay(self, amount: float) -> dict: return {"status": "success", "method": "crypto", "amount": amount, "wallet": self.wallet_address[:8] + "..."} class PaymentProcessor: def __init__(self, strategy: PaymentStrategy): self.strategy = strategy def set_strategy(self, strategy: PaymentStrategy): self.strategy = strategy def process_payment(self, amount: float) -> dict: return self.strategy.pay(amount) # Usage processor = PaymentProcessor(CreditCardPayment("4111111111111111", "123", "12/27")) result = processor.process_payment(99.99)

Problem 2: Write Integration Tests

Write integration tests for a user registration API endpoint.

import pytest import requests BASE_URL = "http://localhost:8000/api" class TestUserRegistration: def test_register_success(self): response = requests.post(f"{BASE_URL}/register", json={ "email": "test@example.com", "password": "SecureP@ss123", "name": "Test User" }) assert response.status_code == 201 data = response.json() assert data["email"] == "test@example.com" assert "password" not in data # Never return password! def test_register_duplicate_email(self): # First registration requests.post(f"{BASE_URL}/register", json={ "email": "dup@example.com", "password": "SecureP@ss123", "name": "User" }) # Duplicate should fail response = requests.post(f"{BASE_URL}/register", json={ "email": "dup@example.com", "password": "SecureP@ss123", "name": "User" }) assert response.status_code == 409 def test_register_weak_password(self): response = requests.post(f"{BASE_URL}/register", json={ "email": "weak@example.com", "password": "123", "name": "Weak User" }) assert response.status_code == 400 assert "password" in response.json()["errors"]

Problem 3: Implement Decorator Pattern

Create decorators for logging, caching, and retry logic.

import time import functools import logging def log_execution(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() logging.info(f"Calling {func.__name__}") result = func(*args, **kwargs) elapsed = time.time() - start logging.info(f"{func.__name__} completed in {elapsed:.3f}s") return result return wrapper def retry(max_attempts=3, delay=1, exceptions=(Exception,)): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts + 1): try: return func(*args, **kwargs) except exceptions as e: if attempt == max_attempts: raise logging.warning(f"Attempt {attempt} failed: {e}. Retrying...") time.sleep(delay * attempt) # Exponential backoff return None return wrapper return decorator def cache_result(ttl=300): def decorator(func): cache = {} @functools.wraps(func) def wrapper(*args): if args in cache: result, timestamp = cache[args] if time.time() - timestamp < ttl: return result result = func(*args) cache[args] = (result, time.time()) return result return wrapper return decorator # Usage @log_execution @retry(max_attempts=3, delay=2) def unreliable_api_call(): import random if random.random() < 0.7: raise ConnectionError("API unavailable") return {"data": "success"} @cache_result(ttl=60) @log_execution def expensive_computation(n): time.sleep(2) return n ** 2

Problem 4: Git Rebase vs Merge

Explain when to use rebase vs merge and demonstrate both workflows.

Answer:

  • Merge: Preserves complete history, creates merge commit. Best for public branches.
  • Rebase: Linear history, no merge commits. Best for feature branches before PR.
# Merge workflow git checkout main git pull origin main git merge feature/user-auth git push origin main # Rebase workflow git checkout feature/user-auth git fetch origin git rebase origin/main # Resolve conflicts if any git push origin feature/user-auth --force-with-lease

Problem 5: Implement a Thread-Safe Queue

Create a producer-consumer queue with proper synchronization.

import threading import queue import time class Pipeline: def __init__(self, max_size=10): self.task_queue = queue.Queue(maxsize=max_size) self.result_queue = queue.Queue() self._shutdown = threading.Event() def producer(self, tasks): for task in tasks: if self._shutdown.is_set(): break self.task_queue.put(task) print(f"Produced: {task}") self.task_queue.put(None) # Sentinel def consumer(self, worker_id): while not self._shutdown.is_set(): try: task = self.task_queue.get(timeout=1) if task is None: self.task_queue.put(None) # Pass sentinel break result = f"Worker {worker_id} processed: {task}" self.result_queue.put(result) self.task_queue.task_done() except queue.Empty: continue def run(self, tasks, num_workers=3): producer_thread = threading.Thread( target=self.producer, args=(tasks,)) consumer_threads = [ threading.Thread(target=self.consumer, args=(i,)) for i in range(num_workers) ] producer_thread.start() for t in consumer_threads: t.start() producer_thread.join() for t in consumer_threads: t.join() results = [] while not self.result_queue.empty(): results.append(self.result_queue.get()) return results

Problem 6: Calculate Cyclomatic Complexity

Write a function that calculates cyclomatic complexity of Python code.

import ast def calculate_complexity(source_code: str) -> dict: """Calculate cyclomatic complexity per function.""" tree = ast.parse(source_code) results = {} for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): complexity = 1 # Base complexity for child in ast.walk(node): if isinstance(child, (ast.If, ast.For, ast.While, ast.ExceptHandler, ast.With, ast.Assert)): complexity += 1 elif isinstance(child, ast.BoolOp): complexity += len(child.values) - 1 results[node.name] = { 'complexity': complexity, 'lines': node.end_lineno - node.lineno } return results # Usage code = ''' def process(items): for item in items: if item > 0: if item % 2 == 0: print("even") else: print("odd") try: result = 10 / len(items) except ZeroDivisionError: result = 0 return result ''' print(calculate_complexity(code)) # {'process': {'complexity': 5, 'lines': 11}}

Problem 7: Design System for Scale

Design a URL shortener service like bit.ly. Include API, database, and caching.

# URL Shortener Design ## API Endpoints # POST /api/shorten โ†’ { "url": "https://..." } โ†’ { "short_code": "abc123" } # GET /{short_code} โ†’ 302 redirect to original URL # GET /api/stats/{short_code} โ†’ click count, referrers ## Database Schema CREATE TABLE urls ( id BIGSERIAL PRIMARY KEY, short_code VARCHAR(10) UNIQUE NOT NULL, original_url TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW(), expires_at TIMESTAMP, user_id INT REFERENCES users(id), click_count BIGINT DEFAULT 0 ); CREATE INDEX idx_short_code ON urls(short_code); ## Architecture # 1. User โ†’ POST /api/shorten with URL # 2. Generate short code (base62 encoding of auto-increment ID) # 3. Store in PostgreSQL + cache in Redis (TTL: 24h) # 4. Return short URL: https://short.link/abc123 # # Redirect Flow: # 1. User visits https://short.link/abc123 # 2. Check Redis cache โ†’ if found, redirect # 3. If cache miss โ†’ query PostgreSQL # 4. Update click count asynchronously (via message queue) # 5. 302 redirect to original URL ## Scaling Considerations # - Use consistent hashing for cache sharding # - Pre-generate short codes in batches # - Read replicas for analytics queries # - CDN for static assets # - Rate limiting per user/IP

Problem 8: Implement Feature Flags

Create a feature flag system for gradual rollouts.

import hashlib from enum import Enum class RolloutStrategy(Enum): BOOLEAN = "boolean" PERCENTAGE = "percentage" USER_LIST = "user_list" class FeatureFlag: def __init__(self, name, strategy, value, description=""): self.name = name self.strategy = strategy self.value = value self.description = description self.enabled = True def is_enabled_for(self, user_id=None): if not self.enabled: return False if self.strategy == RolloutStrategy.BOOLEAN: return self.value elif self.strategy == RolloutStrategy.PERCENTAGE: # Consistent hashing for stable assignment hash_val = int(hashlib.md5( f"{self.name}:{user_id}".encode() ).hexdigest(), 16) return (hash_val % 100) < self.value elif self.strategy == RolloutStrategy.USER_LIST: return user_id in self.value return False class FeatureFlagManager: def __init__(self): self._flags = {} def register(self, flag: FeatureFlag): self._flags[flag.name] = flag def is_enabled(self, name, user_id=None): flag = self._flags.get(name) return flag.is_enabled_for(user_id) if flag else False # Usage manager = FeatureFlagManager() manager.register(FeatureFlag( "new-dashboard", RolloutStrategy.PERCENTAGE, 25, "25% of users see new dashboard" )) manager.register(FeatureFlag( "beta-feature", RolloutStrategy.USER_LIST, ["user-123", "user-456"] )) if manager.is_enabled("new-dashboard", user_id="user-789"): render_new_dashboard() else: render_legacy_dashboard()

Problem 9: Write a Makefile

Create a Makefile for a Python project with common development tasks.

PYTHON := python3 VENV := venv PIP := $(VENV)/bin/pip PYTEST := $(VENV)/bin/pytest .PHONY: help install test lint format clean run migrate help: @echo "Available commands:" @echo " install Install dependencies" @echo " test Run tests with coverage" @echo " lint Run linters" @echo " format Format code" @echo " clean Remove build artifacts" @echo " run Start development server" @echo " migrate Run database migrations" install: $(PYTHON) -m venv $(VENV) $(PIP) install -U pip $(PIP) install -r requirements.txt test: $(PYTEST) --cov=src --cov-report=html --cov-report=term lint: $(VENV)/bin/flake8 src/ $(VENV)/bin/mypy src/ format: $(VENV)/bin/black src/ tests/ $(VENV)/bin/isort src/ tests/ clean: rm -rf __pycache__ .pytest_cache htmlcov dist build *.egg-info find . -name "*.pyc" -delete run: $(VENV)/bin/python -m uvicorn src.main:app --reload --port 8000 migrate: $(VENV)/bin/alembic upgrade head

Problem 10: Implement Event Sourcing

Design an event-sourced system for a banking account.

from dataclasses import dataclass from typing import List from datetime import datetime import uuid @dataclass class DomainEvent: event_id: str aggregate_id: str event_type: str timestamp: datetime data: dict class AccountAggregate: def __init__(self, account_id: str): self.account_id = account_id self.balance = 0 self.version = 0 self._uncommitted_events = [] @classmethod def load_from_history(cls, events: List[DomainEvent]): """Rebuild state from event history.""" if not events: raise ValueError("Cannot load from empty history") account = cls(events[0].aggregate_id) for event in events: account.apply(event) account.version += 1 return account def apply(self, event: DomainEvent): """Apply event to mutate state.""" if event.event_type == "AccountOpened": self.balance = event.data["initial_balance"] elif event.event_type == "MoneyDeposited": self.balance += event.data["amount"] elif event.event_type == "MoneyWithdrawn": self.balance -= event.data["amount"] def deposit(self, amount: float): if amount <= 0: raise ValueError("Amount must be positive") self._uncommitted_events.append(DomainEvent( event_id=str(uuid.uuid4()), aggregate_id=self.account_id, event_type="MoneyDeposited", timestamp=datetime.utcnow(), data={"amount": amount} )) def withdraw(self, amount: float): if amount > self.balance: raise ValueError("Insufficient funds") self._uncommitted_events.append(DomainEvent( event_id=str(uuid.uuid4()), aggregate_id=self.account_id, event_type="MoneyWithdrawn", timestamp=datetime.utcnow(), data={"amount": amount} )) # Event store class EventStore: def __init__(self): self._events = {} # aggregate_id -> [events] def save(self, aggregate_id: str, events: List[DomainEvent]): if aggregate_id not in self._events: self._events[aggregate_id] = [] self._events[aggregate_id].extend(events) def get_events(self, aggregate_id: str) -> List[DomainEvent]: return self._events.get(aggregate_id, [])

๐Ÿ“š Related Subjects

๐Ÿ“š Related Subjects