Rohan Yeole - Homepage Rohan Yeole

Flask vs Django: I Choose Django Every Time, With One Documented Exception

May 20, 20261 min read

For almost every Python web project, I use Django. The decision is not sentimental — it is about the cost of decisions. Flask requires you to make architectural choices that Django has already made: which ORM, how authentication works, how migrations are structured, what the admin interface looks like. Those are not trivial decisions. Making them well takes time; making them badly creates technical debt that compounds.

Flask is the correct choice in one specific scenario. I will describe that scenario precisely, so you can recognise whether it applies to you.

Why I Choose Django for Most Projects

Django's decisions are good decisions

The Django ORM is not the only Python ORM, but it is the most widely used, the most documented, and the one with 20 years of production validation. If I used Flask, I would choose SQLAlchemy — a genuinely excellent ORM — but I would spend time on integration, migration tooling configuration, and session management that Django handles automatically. The output of that time is not product features. It is infrastructure that Django gives away.

The same applies to authentication. Flask-Login is a reasonable library. But Django's authentication system — password hashing, session management, the @login_required decorator, the User model, group-based permissions — is integrated, tested, and documented as a system. Flask-Login handles authentication; Django's auth system handles the whole problem.

The Django admin is underappreciated. For almost every project, someone needs to manage data — view records, edit entries, run operational queries. The Django admin provides this in a few lines of configuration. In Flask, you build it from scratch or pay for a third-party tool. The admin is not glamorous, but the cost of not having it becomes obvious the first time a client needs to correct a data entry manually.

The integration cost in Flask is real and cumulative

Flask extensions work. Flask-SQLAlchemy + Flask-Migrate + Flask-Login + Flask-WTF + Flask-Caching is a legitimate stack. But each extension is configured separately, each has its own version constraints, and they have to be integrated manually. When Flask-SQLAlchemy releases a major version that changes how sessions are managed, you update the integration code. When Flask-Login changes how it stores user IDs in the session, you update the login flow. Each integration point is a maintenance obligation that does not exist in Django because the components are not separate.

I have inherited Flask codebases where the extension versions were frozen years ago because a major upgrade broke three integrations simultaneously and no one had time to fix it. That is not a Flask problem specifically — it is the cost of assembling a framework from parts rather than using one that ships as a system.

The One Case Where Flask Is Correct

I maintain a Flask service that processes incoming webhooks from a payment provider. It receives a POST request, validates the HMAC signature, parses the payload, and publishes a message to a Redis queue. That is the entire application. There are no users. There is no admin interface. There is no data model beyond a Redis connection. The service handles approximately 5,000 requests per day.

For this service, Flask is correct. Here is why:

Django's default WSGI middleware stack includes session middleware, CSRF middleware, authentication middleware, and message middleware. Understanding why this overhead is real requires knowing how WSGI middleware works mechanically: each middleware wraps the application in a callable that intercepts the request before it reaches the view and the response before it leaves. Session middleware reads the session cookie from the request, queries the session backend (Redis or the database) to load session data into request.session, and then saves any modified session data back to the backend on the way out — that's potentially two I/O operations per request. CSRF middleware checks a HMAC token against a value stored in the session. Authentication middleware reads request.session['_auth_user_id'] and queries the User table to populate request.user. None of these apply to a webhook receiver that has no users and no sessions, but all of them run on every request anyway. In Flask, they are absent by default. The service does exactly what it needs to do and nothing else.

The Flask application for this service is approximately 60 lines of code. The equivalent Django service would be similar in code length but would carry a Django project structure — settings, WSGI configuration, URL routing, an installed apps list — for a service that has no apps, no models, and no admin.

The pattern that defines the exception:

  • No user authentication
  • No data model beyond a single table or no persistence at all
  • No admin interface
  • The service is a single-purpose component in a larger system (webhook receiver, API proxy, queue publisher)
  • The service will not grow in scope — the requirement is fixed and unlikely to change

If all five of those are true, Flask is probably the right call. If any of them are uncertain, choose Django.

Why "We'll Keep It Simple" Is the Wrong Reason to Choose Flask

The most common reason Flask gets chosen over Django is the assumption that the project will stay simple. Flask feels lighter — the hello-world application is five lines, there is no project scaffolding, no settings file, no installed apps. Starting is faster.

The assumption breaks when requirements change. And requirements almost always change.

The service that was "just a webhook receiver" adds a requirement to store webhook history for auditing. That means a database, migrations, and potentially an admin view for the operations team. The "simple internal API" adds user-based access control. The "minimal dashboard" gets a login flow. Each of these additions is straightforward in Django. In Flask, each one requires choosing an extension, integrating it, and solving the problems that come from that integration.

The cost of migrating from Flask to Django mid-project — after a schema exists, after Flask-specific patterns are established, after the team has built muscle memory around the Flask way of doing things — is two to four weeks of no feature work. I have paid that cost, and the client paid for it. The lesson is: if there is any realistic path to the project needing users, admin, or a growing data model, start with Django.

The Performance Argument Is Not the Deciding Factor

Flask applications are marginally faster than Django applications under identical conditions because Django's middleware stack adds processing per request. The difference is measurable in benchmarks and invisible in production for the overwhelming majority of web applications.

At 1,000 requests per second — a level most applications never approach — the difference between Django's 3ms average response time and Flask's 2.5ms average response time is 0.5ms. At 100 requests per second — more typical for applications that are not at massive scale — the difference is irrelevant. The bottleneck for almost every web application is the database, not the framework.

If you are building a service where framework performance genuinely matters — hundreds of thousands of requests per second, latency-critical paths — neither Flask nor Django is the right choice. You would be evaluating FastAPI (async Python), Go, or Rust.

Choosing Between Them

Use Django when: - The project has users, authentication, or role-based permissions - You need an admin interface for data management - The data model has more than two or three tables - The team will change over time and you want standardised conventions - You anticipate the requirements growing

Use Flask when: - The service is genuinely single-purpose and the scope is fixed - There are no users and no authentication requirement - The service is a component in a microservices architecture with a specific, bounded responsibility - The team strongly prefers assembling their own stack and has the experience to do it well

Frequently Asked Questions

Is Flask or Django better for a REST API? Django with Django REST Framework is the more complete choice for a REST API that will grow — DRF's serialisers, viewsets, authentication classes, and throttling are mature and well-documented. Flask is simpler for a small API with a fixed set of endpoints, but as the API grows, the lack of structure becomes a liability. FastAPI is worth considering as a third option for new APIs — it has async support and automatic OpenAPI documentation from the start.

Can Flask scale to large applications? Technically yes — Flask scales with the right architecture. But "can it scale" is the wrong question. The right question is "should it scale in this direction?" Large Flask applications require strong architectural discipline to avoid the problems that a framework like Django prevents through convention. Teams that have maintained large Flask applications successfully are usually ones where a senior developer made and enforced consistent architectural decisions throughout. That is a riskier bet than starting with a framework that enforces those decisions for you.

What about Jinja2 templates — is that the same in both? Yes. Jinja2 is used by both Flask (as the default templating engine) and Django (Django's template language is based on Jinja2 with Django-specific additions). A developer fluent in one can read the other without relearning template syntax.

Should I rewrite my Flask app in Django? Only if the cost of the Flask extension debt — updating integrations, debugging incompatibilities, assembling features that Django includes — exceeds the cost of the rewrite. For small applications (under 5,000 lines), a rewrite in Django is often a month of work and pays back quickly. For large applications, a gradual migration is usually more practical: move new features to a parallel Django application and deprecate Flask modules over time.

Chat with me on WhatsApp