🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

FastAPI vs Django vs Flask: Architecture, Performance, async

PythonWeb Frameworks⭐ Premium

Advertisement

Google, Meta & Amazon Interview

FastAPI vs Django vs Flask: Architecture, Performance, async

Choosing the right Python web framework

Interview Question

"Compare FastAPI, Django, and Flask. What are the architectural differences? When would you use each? How do they handle async, performance, and scalability?"

Difficulty: Medium | Frequently asked at Google, Meta, Amazon


Theoretical Foundation

Framework Overview

# Flask: Micro-framework, minimal, flexible
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/api/users')
def get_users():
    return jsonify([{"id": 1, "name": "Alice"}])

# Django: Full-featured, batteries included
from django.http import JsonResponse
def get_users(request):
    return JsonResponse([{"id": 1, "name": "Alice"}], safe=False)

# FastAPI: Modern, async, automatic docs
from fastapi import FastAPI
app = FastAPI()

@app.get("/api/users")
async def get_users():
    return [{"id": 1, "name": "Alice"}]

ℹ️

Key Concept: Each framework has different philosophies: Flask (minimal), Django (batteries included), FastAPI (modern async).


Flask

Basic Flask Application

from flask import Flask, request, jsonify, g
from functools import wraps
import time

app = Flask(__name__)

# Middleware
@app.before_request
def before_request():
    g.start_time = time.time()

@app.after_request
def after_request(response):
    if hasattr(g, 'start_time'):
        elapsed = time.time() - g.start_time
        response.headers['X-Response-Time'] = f'{elapsed:.3f}s'
    return response

# Routes
@app.route('/api/users', methods=['GET'])
def get_users():
    """Get all users."""
    users = [
        {"id": 1, "name": "Alice", "email": "alice@example.com"},
        {"id": 2, "name": "Bob", "email": "bob@example.com"}
    ]
    return jsonify(users)

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    """Get user by ID."""
    user = {"id": user_id, "name": "Alice"}
    return jsonify(user)

@app.route('/api/users', methods=['POST'])
def create_user():
    """Create a new user."""
    data = request.get_json()
    if not data or 'name' not in data:
        return jsonify({"error": "Name required"}), 400
    
    user = {"id": 1, **data}
    return jsonify(user), 201

# Error handlers
@app.errorhandler(404)
def not_found(error):
    return jsonify({"error": "Not found"}), 404

@app.errorhandler(500)
def server_error(error):
    return jsonify({"error": "Internal server error"}), 500

# Run
if __name__ == '__main__':
    app.run(debug=True)

Flask with Extensions

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_restful import Api, Resource
from flask_caching import Cache

app = Flask(__name__)

# Configuration
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['CACHE_TYPE'] = 'simple'

# Initialize extensions
db = SQLAlchemy(app)
ma = Marshmallow(app)
api = Api(app)
cache = Cache(app)

# Models
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)

# Schemas
class UserSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = User

# RESTful API
class UserResource(Resource):
    @cache.cached(timeout=60)
    def get(self, user_id):
        user = User.query.get_or_404(user_id)
        return UserSchema().dump(user)
    
    def put(self, user_id):
        user = User.query.get_or_404(user_id)
        data = request.get_json()
        user.name = data.get('name', user.name)
        db.session.commit()
        return UserSchema().dump(user)

api.add_resource(UserResource, '/api/users/<int:user_id>')

# Run
if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

💡

Interview Tip: Flask is great for microservices and small APIs, but requires more setup for large applications.


Django

Basic Django Application

# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import get_object_or_404
import json

# Models
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['-created_at']

# Serializers
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email', 'created_at']
        read_only_fields = ['id', 'created_at']

# Views (Function-based)
@csrf_exempt
@require_http_methods(["GET", "POST"])
def user_list(request):
    if request.method == "GET":
        users = User.objects.all()
        serializer = UserSerializer(users, many=True)
        return JsonResponse(serializer.data, safe=False)
    
    elif request.method == "POST":
        data = json.loads(request.body)
        serializer = UserSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

# Views (Class-based)
from django.views import View
from django.utils.decorators import method_decorator

@method_decorator(csrf_exempt, name='dispatch')
class UserViewSet(View):
    def get(self, request, user_id=None):
        if user_id:
            user = get_object_or_404(User, id=user_id)
            return JsonResponse(UserSerializer(user).data)
        users = User.objects.all()
        return JsonResponse(UserSerializer(users, many=True).data, safe=False)
    
    def post(self, request):
        data = json.loads(request.body)
        serializer = UserSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

# URLs
from django.urls import path

urlpatterns = [
    path('api/users/', user_list),
    path('api/users/<int:user_id>/', UserViewSet.as_view()),
]

Django REST Framework

# serializers.py
from rest_framework import serializers
from .models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email', 'created_at']
    
    def validate_email(self, value):
        if User.objects.filter(email=value).exists():
            raise serializers.ValidationError("Email already exists")
        return value

# views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name', 'email']
    
    @action(detail=False, methods=['get'])
    def active(self, request):
        users = self.queryset.filter(is_active=True)
        serializer = self.get_serializer(users, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post'])
    def deactivate(self, request, pk=None):
        user = self.get_object()
        user.is_active = False
        user.save()
        return Response({'status': 'deactivated'})

# urls.py
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = router.urls

ℹ️

Django Advantage: Built-in admin, ORM, authentication, and security features save development time.


FastAPI

Basic FastAPI Application

from fastapi import FastAPI, HTTPException, Depends, Query, Path
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
from datetime import datetime
import uvicorn

app = FastAPI(title="User API", version="1.0.0")

# Pydantic models
class UserBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr

class UserCreate(UserBase):
    pass

class UserResponse(UserBase):
    id: int
    created_at: datetime
    
    class Config:
        from_attributes = True

# Dependency injection
async def get_db():
    """Database dependency."""
    db = {"connection": True}
    try:
        yield db
    finally:
        db["connection"] = False

async def get_current_user(token: str = Header(...)):
    """Auth dependency."""
    if token != "valid_token":
        raise HTTPException(status_code=401, detail="Invalid token")
    return {"id": 1, "name": "Alice"}

# Routes
@app.get("/api/users", response_model=List[UserResponse])
async def get_users(
    skip: int = Query(0, ge=0),
    limit: int = Query(100, ge=1, le=1000),
    db: dict = Depends(get_db)
):
    """Get all users with pagination."""
    users = [
        {"id": 1, "name": "Alice", "email": "alice@example.com", "created_at": datetime.now()},
        {"id": 2, "name": "Bob", "email": "bob@example.com", "created_at": datetime.now()}
    ]
    return users[skip:skip+limit]

@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: int = Path(..., gt=0),
    current_user: dict = Depends(get_current_user)
):
    """Get user by ID."""
    if user_id == 999:
        raise HTTPException(status_code=404, detail="User not found")
    return {"id": user_id, "name": "Alice", "email": "alice@example.com", "created_at": datetime.now()}

@app.post("/api/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate, db: dict = Depends(get_db)):
    """Create a new user."""
    return {"id": 1, **user.dict(), "created_at": datetime.now()}

# Error handling
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail}
    )

# Run
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

FastAPI with Async

from fastapi import FastAPI
from typing import AsyncGenerator
import asyncio
import httpx

app = FastAPI()

# Async database operations
async def async_fetch_users():
    """Simulate async database fetch."""
    await asyncio.sleep(0.1)  # Simulate I/O
    return [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"}
    ]

# Async HTTP client
async def fetch_external_api(url: str) -> dict:
    """Fetch from external API."""
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.json()

# Async generator for streaming
async def stream_users() -> AsyncGenerator[dict, None]:
    """Stream users one by one."""
    for i in range(10):
        await asyncio.sleep(0.1)
        yield {"id": i, "name": f"User_{i}"}

@app.get("/api/users/stream")
async def stream_users_endpoint():
    """Stream users endpoint."""
    return StreamingResponse(
        stream_users(),
        media_type="application/json"
    )

@app.get("/api/users/async")
async def get_users_async():
    """Async endpoint."""
    users = await async_fetch_users()
    return users

@app.get("/api/users/{user_id}/external")
async def get_user_external(user_id: int):
    """Fetch user from external API."""
    data = await fetch_external_api(f"https://api.example.com/users/{user_id}")
    return data

FastAPI Background Tasks

from fastapi import FastAPI, BackgroundTasks
from typing import Optional
import asyncio

app = FastAPI()

# Background task function
def write_log(message: str):
    """Write to log file."""
    with open("app.log", "a") as f:
        f.write(f"{message}\n")

async def process_data(data: dict):
    """Process data asynchronously."""
    await asyncio.sleep(1)  # Simulate processing
    print(f"Processed: {data}")

@app.post("/api/users")
async def create_user(user: dict, background_tasks: BackgroundTasks):
    """Create user with background processing."""
    # Add background tasks
    background_tasks.add_task(write_log, f"Created user: {user}")
    background_tasks.add_task(process_data, user)
    
    return {"status": "created", "user": user}

@app.on_event("startup")
async def startup():
    """Startup event."""
    print("Starting up...")

@app.on_event("shutdown")
async def shutdown():
    """Shutdown event."""
    print("Shutting down...")

💡

FastAPI Advantage: Automatic API docs, type validation, and native async support make it ideal for modern APIs.


Comparison

Performance Benchmark

# Benchmark results (approximate)

# Requests per second (higher is better)
benchmarks = {
    "Flask": {"requests_sec": 1000, "latency_ms": 5.0},
    "Django": {"requests_sec": 800, "latency_ms": 6.5},
    "FastAPI": {"requests_sec": 3000, "latency_ms": 1.5},
}

print("Performance Comparison:")
print("-" * 50)
for framework, metrics in benchmarks.items():
    print(f"{framework:10}: {metrics['requests_sec']:5} req/s, {metrics['latency_ms']:.1f}ms latency")

Output:

Architecture Diagram
Performance Comparison:
--------------------------------------------------
Flask     :  1000 req/s, 5.0ms latency
Django    :   800 req/s, 6.5ms latency
FastAPI   :  3000 req/s, 1.5ms latency

Feature Comparison

FeatureFlaskDjangoFastAPI
Learning curveLowMediumMedium
Async supportExperimentalLimitedNative
API docsManualDRFAuto
ValidationManualDRFPydantic
ORMSQLAlchemyBuilt-inSQLAlchemy
AdminManualBuilt-inManual
AuthExtensionsBuilt-inOAuth2
Real-timeManualChannelsWebSocket
ScalabilityHighHighVery High

When to Use What?

# Flask:
# - Microservices
# - Small to medium APIs
# - Rapid prototyping
# - Learning web development

# Django:
# - Full-stack applications
# - Content management systems
# - E-commerce platforms
# - Enterprise applications

# FastAPI:
# - High-performance APIs
# - Microservices architecture
# - Real-time applications
# - ML model serving

ℹ️

Decision Framework: Choose based on project size, performance needs, and team expertise.


Real-World Patterns

Authentication

# Flask
from flask import Flask, request, jsonify
from functools import wraps
import jwt

app = Flask(__name__)

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        if not token:
            return jsonify({'message': 'Token missing'}), 401
        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            current_user = data['user_id']
        except:
            return jsonify({'message': 'Invalid token'}), 401
        return f(current_user, *args, **kwargs)
    return decorated

# Django
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def protected_view(request):
    return Response({'message': 'Hello, world!'})

# FastAPI
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    if not verify_token(token):
        raise HTTPException(status_code=401)
    return decode_token(token)

@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
    return {"message": f"Hello, {current_user['name']}"}

Database Patterns

# Flask + SQLAlchemy
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))

# Django ORM
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)

# FastAPI + SQLAlchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100))

Interview Tips

Common Follow-up Questions

  1. "When would you choose Flask over Django?"

    • Microservices architecture
    • Need for minimal overhead
    • Custom architecture requirements
    • Small team with Python expertise
  2. "How does FastAPI achieve better performance?"

    • Starlette (async framework)
    • Pydantic (C-based validation)
    • No ORM overhead
    • Native async/await
  3. "What are the scalability considerations?"

    • Flask: Horizontal scaling, load balancing
    • Django: Database optimization, caching
    • FastAPI: Async I/O, connection pooling

Code Review Tips

# BAD: No input validation (Flask)
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    user = User(**data)  # No validation!
    return jsonify(user)

# GOOD: With validation (FastAPI)
@app.post("/api/users")
async def create_user(user: UserCreate):  # Pydantic validates
    return {"status": "created"}

# BAD: Synchronous I/O in async (FastAPI)
@app.get("/api/users")
async def get_users():
    users = db.query(User).all()  # Blocks event loop!
    return users

# GOOD: Async database (FastAPI)
@app.get("/api/users")
async def get_users():
    async with db.session() as session:
        result = await session.execute(select(User))
        return result.scalars().all()

⚠️

Common Mistake: Using synchronous I/O in async frameworks blocks the event loop and reduces performance.


Summary

FrameworkBest ForPerformanceLearning Curve
FlaskMicroservices, prototypingGoodLow
DjangoFull-stack, enterpriseGoodMedium
FastAPIHigh-performance APIsExcellentMedium

Decision Matrix

RequirementRecommended
Rapid prototypingFlask
Full-stack appDjango
High-performance APIFastAPI
Real-time featuresFastAPI
Admin interfaceDjango
MicroservicesFastAPI or Flask
ML model servingFastAPI

ℹ️

Key Takeaway: Choose the framework that fits your project needs. All three are excellent for different use cases.


Practice Problems

  1. Build API: Create a REST API with all three frameworks
  2. Compare Performance: Benchmark identical endpoints
  3. Authentication: Implement JWT auth in each framework
  4. Database Integration: Set up ORM with each framework
  5. Real-time: Add WebSocket support to FastAPI

Further Reading

Remember: The best framework is the one that fits your project requirements and team expertise.

Advertisement