Web Frameworks: Flask vs Django vs FastAPI

0/4 in this phase0/54 across the roadmap

📖 Concept

Python offers three dominant web frameworks, each with a distinct philosophy and sweet spot. Understanding their differences is critical for choosing the right tool and answering interview questions about architecture trade-offs.

Comparison Table:

Feature Flask Django FastAPI
Philosophy Micro-framework, minimal core Batteries-included Modern, async-first
Server Interface WSGI WSGI (ASGI via Channels) ASGI (native async)
ORM None (use SQLAlchemy) Built-in Django ORM None (use SQLAlchemy)
Admin Panel None Built-in None
Validation Manual / WTForms Django Forms / DRF serializers Pydantic (automatic)
Auto Docs No (add Swagger manually) No (add via DRF) Yes (OpenAPI + Swagger UI)
Async Support Limited (async views in 2.0+) Partial (views, ORM still sync) Full native async/await
Learning Curve Low High Medium
Best For Microservices, prototypes Full-stack apps, CMS, admin-heavy APIs, high-concurrency services

WSGI vs ASGI:

  • WSGI (Web Server Gateway Interface) is the traditional synchronous protocol. One request per thread. Servers: Gunicorn, uWSGI.
  • ASGI (Asynchronous Server Gateway Interface) supports async, WebSockets, and long-lived connections natively. Servers: Uvicorn, Daphne, Hypercorn.

When to use each:

  • Flask — small APIs, microservices, when you want full control over every dependency. Perfect for teams that prefer explicit over implicit.
  • Django — content-heavy sites, admin dashboards, projects needing auth, ORM, migrations, and templating out of the box. Ideal when development speed matters more than micro-optimization.
  • FastAPI — high-performance REST/GraphQL APIs, real-time applications, ML model serving. Best when you need automatic validation, serialization, and interactive documentation.

In interviews, emphasize that the choice depends on project requirements — not personal preference. A monolithic Django app and a FastAPI microservice solve different problems.

💻 Code Example

codeTap to expand ⛶
1# ============================================================
2# FlaskMinimal "Hello World" API
3# ============================================================
4# from flask import Flask, jsonify, request, abort
5#
6# app = Flask(__name__)
7#
8# # In-memory store (replaced by a database in production)
9# books = [
10# {"id": 1, "title": "Clean Code", "author": "Robert Martin"},
11# {"id": 2, "title": "Pragmatic Programmer", "author": "Hunt & Thomas"},
12# ]
13#
14#
15# @app.route("/api/books", methods=["GET"])
16# def get_books():
17# """GET /api/books — return all books."""
18# return jsonify(books)
19#
20#
21# @app.route("/api/books/<int:book_id>", methods=["GET"])
22# def get_book(book_id):
23# """GET /api/books/:id — return single book or 404."""
24# book = next((b for b in books if b["id"] == book_id), None)
25# if book is None:
26# abort(404, description="Book not found")
27# return jsonify(book)
28#
29#
30# @app.route("/api/books", methods=["POST"])
31# def create_book():
32# """POST /api/books — create a new book."""
33# data = request.get_json()
34# if not data or "title" not in data:
35# abort(400, description="Title is required")
36# new_book = {
37# "id": max(b["id"] for b in books) + 1 if books else 1,
38# "title": data["title"],
39# "author": data.get("author", "Unknown"),
40# }
41# books.append(new_book)
42# return jsonify(new_book), 201
43#
44#
45# # ============================================================
46# # DjangoViews equivalent (views.py in a Django app)
47# # ============================================================
48# # BAD: Fat views with no separation of concerns
49# # from django.http import JsonResponse
50# # from django.views import View
51# #
52# # class BookView(View):
53# # def get(self, request):
54# # books = list(Book.objects.values("id", "title", "author"))
55# # return JsonResponse(books, safe=False)
56# # def post(self, request):
57# # import json
58# # data = json.loads(request.body)
59# # book = Book.objects.create(**data) # No validation!
60# # return JsonResponse({"id": book.id}, status=201)
61#
62# # GOOD: Django REST Framework serializer-based views
63# # from rest_framework import viewsets, serializers
64# # from .models import Book
65# #
66# # class BookSerializer(serializers.ModelSerializer):
67# # class Meta:
68# # model = Book
69# # fields = ["id", "title", "author"]
70# #
71# # class BookViewSet(viewsets.ModelViewSet):
72# # queryset = Book.objects.all()
73# # serializer_class = BookSerializer
74# # # Gives you GET, POST, PUT, PATCH, DELETE for free
75#
76#
77# # ============================================================
78# # FastAPISame API with automatic validation & docs
79# # ============================================================
80# from fastapi import FastAPI, HTTPException
81# from pydantic import BaseModel, Field
82#
83# app = FastAPI(title="Book API", version="1.0.0")
84#
85#
86# class BookCreate(BaseModel):
87# """Pydantic model = automatic validation + serialization."""
88# title: str = Field(..., min_length=1, max_length=200)
89# author: str = Field(default="Unknown", max_length=100)
90#
91#
92# class BookResponse(BookCreate):
93# id: int
94#
95#
96# books_db: list[BookResponse] = []
97#
98#
99# @app.get("/api/books", response_model=list[BookResponse])
100# async def get_books():
101# return books_db
102#
103#
104# @app.get("/api/books/{book_id}", response_model=BookResponse)
105# async def get_book(book_id: int):
106# book = next((b for b in books_db if b.id == book_id), None)
107# if not book:
108# raise HTTPException(status_code=404, detail="Book not found")
109# return book
110#
111#
112# @app.post("/api/books", response_model=BookResponse, status_code=201)
113# async def create_book(book: BookCreate):
114# # Pydantic validates the request body automatically
115# new_id = max((b.id for b in books_db), default=0) + 1
116# new_book = BookResponse(id=new_id, **book.model_dump())
117# books_db.append(new_book)
118# return new_book
119#
120#
121# # Run: uvicorn main:app --reload
122# # Docs: http://127.0.0.1:8000/docs (Swagger UI auto-generated)

🏋️ Practice Exercise

Exercises:

  1. Build the same CRUD API (Create, Read, Update, Delete for a `Task` model with `id`, `title`, `done`, `created_at` fields) in all three frameworks: Flask, Django REST Framework, and FastAPI. Compare the total lines of code, validation handling, and error responses.

  2. Add pagination to each framework's list endpoint. Implement `?page=1&per_page=10` query parameters. Compare how each framework handles query parameter parsing and validation.

  3. Create a Flask Blueprint and a Django app that both serve a `/health` endpoint returning `{"status": "ok", "uptime": }`. Demonstrate how each framework organizes modular code.

  4. Write a FastAPI app with automatic OpenAPI documentation. Add custom examples to Pydantic models using `model_config` so that the Swagger UI shows realistic sample data.

  5. Deploy your FastAPI app behind Uvicorn with Gunicorn as the process manager. Benchmark it with `wrk` or `hey` and compare throughput against the Flask equivalent running under Gunicorn with sync workers.

⚠️ Common Mistakes

  • Using Flask for a project that needs auth, admin, ORM, and migrations out of the box — Django would save weeks of integration work. Choose the framework that matches your project scope, not the one you are most comfortable with.

  • Running FastAPI with a WSGI server like Gunicorn without Uvicorn workers. FastAPI requires an ASGI server. Use uvicorn main:app or gunicorn main:app -k uvicorn.workers.UvicornWorker for production.

  • Blocking the event loop in FastAPI async handlers by calling synchronous I/O (e.g., time.sleep(), synchronous DB queries). Use await asyncio.sleep(), async DB drivers, or declare the handler as def (not async def) so FastAPI runs it in a thread pool.

  • Not validating request data in Flask. Unlike FastAPI (Pydantic) or Django (Forms/Serializers), Flask does zero automatic validation. Always validate request.get_json() manually or use a library like Marshmallow.

  • Ignoring Django's CSRF protection when building APIs. For token-based APIs, use Django REST Framework's authentication classes and explicitly exempt views from CSRF where appropriate, rather than disabling middleware globally.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Web Frameworks: Flask vs Django vs FastAPI