Flask interviews test Python fluency, HTTP fundamentals, REST API design, and the ability to work with a micro-framework without the guardrails of batteries-included frameworks like Django. This guide covers 50 of the most common Flask interview questions with concise answers and code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Core concepts | WSGI, application context, request context |
| Routing | URL rules, variable rules, url_for |
| Templates | Jinja2 filters, template inheritance, render_template |
| Request/Response | request object, make_response, cookies, redirects |
| Blueprints | Modular apps, blueprint registration |
| REST APIs | JSON responses, status codes, Flask-RESTful |
| Database | SQLAlchemy, Flask-Migrate, ORM patterns |
| Auth | Session, Flask-Login, JWT |
| Testing | test_client, fixtures, pytest |
| Production | Gunicorn, config patterns, error handlers |
Core concepts
1. What is Flask and how does it differ from Django?
Flask is a lightweight WSGI web framework written in Python. It provides routing, request handling, and a templating engine (Jinja2) — nothing more by default. Everything else (ORM, auth, admin) you choose yourself.
| Feature | Flask | Django |
|---|---|---|
| Philosophy | Micro-framework | Batteries-included |
| ORM | Bring your own (e.g., SQLAlchemy) | Built-in Django ORM |
| Admin panel | Third-party or custom | Built-in |
| Auth | Flask-Login / JWT | Built-in auth system |
| Project structure | Flexible | Convention-driven |
| Learning curve | Gentle | Steeper but opinionated |
| Best for | APIs, microservices, custom stacks | Full-featured web apps |
2. What is WSGI and why does Flask use it?
WSGI (Web Server Gateway Interface — PEP 3333) is the Python standard for communication between a web server (e.g., Nginx, Gunicorn) and a Python web application.
# Minimal WSGI app — Flask replaces all this boilerplate
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Hello, World!']
Flask implements WSGI so you can deploy it behind any WSGI-compatible server. For async workloads Flask also supports ASGI via asgiref.
3. What is the application context vs the request context?
Flask maintains two context stacks:
| Context | Object | Pushed when | Provides |
|---|---|---|---|
| Application | g, current_app |
App starts / app.app_context() |
App-level state (DB connection, config) |
| Request | request, session |
HTTP request arrives | Per-request data |
from flask import current_app, g, request
@app.route('/info')
def info():
# request context is active here automatically
print(request.method) # 'GET'
print(current_app.name) # app name
g.user = 'Alice' # per-request scratch space
return 'ok'
# Manual app context (e.g., CLI commands, tests)
with app.app_context():
do_database_work()
4. What is g in Flask?
g ("global") is a per-request namespace — a plain object where you can store data for the duration of a single request. It is reset for every new request.
Common use: store the current user after loading it from the database once, then reuse it in multiple helper functions during the same request.
from flask import g, request
@app.before_request
def load_user():
user_id = request.cookies.get('user_id')
g.user = User.query.get(user_id) if user_id else None
5. How do you create a minimal Flask application?
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, Flask!'
if __name__ == '__main__':
app.run(debug=True)
Flask(__name__) tells Flask where to find templates and static files relative to the module.
Routing
6. How does Flask routing work?
Flask maps URLs to Python functions using the @app.route() decorator, which calls app.add_url_rule() internally.
@app.route('/users/<int:user_id>', methods=['GET', 'PUT', 'DELETE'])
def user_detail(user_id):
...
Variable converters:
| Converter | Example | Matches |
|---|---|---|
string (default) |
<name> |
Any text without a slash |
int |
<int:id> |
Positive integers |
float |
<float:value> |
Positive floats |
path |
<path:subpath> |
Like string but includes slashes |
uuid |
<uuid:token> |
UUID strings |
7. What is url_for and why should you use it?
url_for generates a URL for a given view function by name, avoiding hardcoded strings that break when routes change.
from flask import url_for, redirect
@app.route('/login')
def login():
...
@app.route('/dashboard')
def dashboard():
return redirect(url_for('login')) # '/login'
# With parameters
url_for('user_detail', user_id=42) # '/users/42'
# In Jinja2 templates
# <a href="{{ url_for('index') }}">Home</a>
8. What is the difference between redirect and abort?
from flask import redirect, abort, url_for
@app.route('/old')
def old():
return redirect(url_for('new'), 301) # permanent redirect
@app.route('/admin')
def admin():
if not current_user.is_admin:
abort(403) # raise HTTP error immediately
...
redirect sends the client to a new URL. abort immediately stops processing and raises an HTTP exception (handled by error handlers or Flask's default error pages).
9. How do you handle HTTP methods in Flask?
@app.route('/items', methods=['GET', 'POST'])
def items():
if request.method == 'GET':
return jsonify(items_list)
if request.method == 'POST':
data = request.get_json()
# create item...
return jsonify(new_item), 201
Or use MethodView for class-based views:
from flask.views import MethodView
class ItemView(MethodView):
def get(self):
return jsonify(items_list)
def post(self):
...
app.add_url_rule('/items', view_func=ItemView.as_view('items'))
Templates
10. What templating engine does Flask use?
Flask uses Jinja2 — a fast, sandboxed templating engine for Python.
Key syntax:
{# Comment #}
{{ variable }} {# output #}
{% if condition %}...{% endif %}
{% for item in items %}...{% endfor %}
{% block content %}{% endblock %}
{% extends "base.html" %}
{% include "partial.html" %}
{{ name | upper | truncate(20) }} {# filters #}
11. What is template inheritance in Jinja2?
Template inheritance lets child templates override blocks defined in a parent template.
<!-- base.html -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}My App{% endblock %}</title></head>
<body>
{% block content %}{% endblock %}
</body>
</html>
<!-- page.html -->
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome!</h1>
{% endblock %}
12. How do you pass data to templates?
from flask import render_template
@app.route('/profile/<username>')
def profile(username):
user = User.query.filter_by(username=username).first_or_404()
return render_template('profile.html', user=user, title='Profile')
Jinja2 also has access to request, session, g, config, and url_for automatically via Flask's template context.
13. What are Jinja2 filters and how do you create a custom one?
Filters transform values in templates: {{ value | filter_name }}.
# Register a custom filter
@app.template_filter('currency')
def currency_filter(value, symbol='$'):
return f'{symbol}{value:,.2f}'
{{ product.price | currency }} {# $1,234.56 #}
{{ product.price | currency('€') }} {# €1,234.56 #}
Built-in filters: upper, lower, title, capitalize, trim, truncate, join, length, default, safe, escape, replace, tojson.
Request and Response
14. What data does the request object expose?
from flask import request
request.method # 'GET', 'POST', etc.
request.args # URL query params (?key=val) — ImmutableMultiDict
request.form # POST form data — ImmutableMultiDict
request.json # parsed JSON body (Content-Type: application/json)
request.get_json() # safer JSON parse with force/silent options
request.files # uploaded files — ImmutableMultiDict of FileStorage
request.cookies # dict of cookies
request.headers # HTTP headers
request.url # full URL
request.path # URL path only
request.host # hostname
request.remote_addr # client IP
request.is_json # True if Content-Type is JSON
15. How do you return JSON from a Flask view?
from flask import jsonify
@app.route('/api/user/<int:uid>')
def get_user(uid):
user = User.query.get_or_404(uid)
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email
})
# Custom status code + headers
return jsonify({'error': 'Not found'}), 404
# Flask 2.2+ — return dict directly (auto-converted to JSON)
return {'id': 1, 'name': 'Alice'}, 200
16. How do you set cookies and read them?
from flask import request, make_response
@app.route('/set-cookie')
def set_cookie():
resp = make_response('Cookie set!')
resp.set_cookie(
'user_id', '42',
max_age=60*60*24*30, # 30 days
httponly=True,
samesite='Lax',
secure=True # HTTPS only
)
return resp
@app.route('/read-cookie')
def read_cookie():
user_id = request.cookies.get('user_id', 'anonymous')
return f'Hello user {user_id}'
17. What are Flask sessions?
Flask sessions store data client-side in a signed cookie (using the SECRET_KEY). The data is base64-encoded and signed — tamper-proof but not encrypted (visible to clients).
from flask import session
app.secret_key = 'your-secret-key' # required
@app.route('/login', methods=['POST'])
def login():
session['user_id'] = verified_user.id
session.permanent = True # respect PERMANENT_SESSION_LIFETIME
return redirect(url_for('dashboard'))
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
For sensitive data or large payloads, use server-side sessions (Flask-Session with Redis/database backend).
Blueprints
18. What are Flask Blueprints?
Blueprints are modular components that group related routes, templates, and static files. They allow you to split a large Flask app into smaller, reusable pieces.
# auth/routes.py
from flask import Blueprint
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login')
def login():
...
@auth_bp.route('/logout')
def logout():
...
# app.py
from auth.routes import auth_bp
from api.routes import api_bp
app = Flask(__name__)
app.register_blueprint(auth_bp)
app.register_blueprint(api_bp, url_prefix='/api/v1')
19. What is the typical Flask project structure for a medium app?
myapp/
├── app/
│ ├── __init__.py # create_app() factory
│ ├── auth/
│ │ ├── __init__.py
│ │ └── routes.py # Blueprint
│ ├── api/
│ │ ├── __init__.py
│ │ └── routes.py
│ ├── models.py
│ ├── extensions.py # db = SQLAlchemy(), login_manager = ...
│ └── templates/
├── tests/
├── migrations/
├── config.py
├── requirements.txt
└── wsgi.py
20. What is the Application Factory pattern?
Instead of creating app = Flask(__name__) at module level (which makes testing hard), create a factory function:
# app/__init__.py
from flask import Flask
from .extensions import db, login_manager
from .auth.routes import auth_bp
from .api.routes import api_bp
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
login_manager.init_app(app)
app.register_blueprint(auth_bp)
app.register_blueprint(api_bp)
return app
This enables:
- Multiple app instances (testing vs. production)
- Avoiding circular imports
- Clean extension initialization
REST APIs
21. How do you build a REST API with Flask?
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
items = {}
@app.route('/api/items', methods=['GET'])
def list_items():
return jsonify(list(items.values()))
@app.route('/api/items', methods=['POST'])
def create_item():
data = request.get_json(force=True)
if not data or 'name' not in data:
abort(400)
item = {'id': len(items) + 1, 'name': data['name']}
items[item['id']] = item
return jsonify(item), 201
@app.route('/api/items/<int:item_id>', methods=['GET'])
def get_item(item_id):
return jsonify(items.get(item_id) or abort(404))
@app.route('/api/items/<int:item_id>', methods=['PUT'])
def update_item(item_id):
if item_id not in items:
abort(404)
data = request.get_json(force=True)
items[item_id].update(data)
return jsonify(items[item_id])
@app.route('/api/items/<int:item_id>', methods=['DELETE'])
def delete_item(item_id):
items.pop(item_id, None) or abort(404)
return '', 204
22. How do you handle errors in Flask?
from flask import jsonify
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(422)
def unprocessable(error):
return jsonify({'error': 'Unprocessable entity'}), 422
@app.errorhandler(Exception)
def handle_exception(e):
app.logger.error(f'Unhandled exception: {e}', exc_info=True)
return jsonify({'error': 'Internal server error'}), 500
23. How do you validate request data in Flask?
Common approaches:
# Option 1: marshmallow
from marshmallow import Schema, fields, ValidationError
class UserSchema(Schema):
name = fields.Str(required=True, validate=lambda x: len(x) > 0)
email = fields.Email(required=True)
age = fields.Int(load_default=None)
@app.route('/users', methods=['POST'])
def create_user():
try:
data = UserSchema().load(request.get_json())
except ValidationError as err:
return jsonify(err.messages), 422
...
# Option 2: pydantic (v2)
from pydantic import BaseModel, EmailStr
class UserIn(BaseModel):
name: str
email: EmailStr
age: int | None = None
@app.route('/users', methods=['POST'])
def create_user():
try:
user = UserIn.model_validate(request.get_json())
except ValueError as e:
return jsonify({'error': str(e)}), 422
...
24. What is CORS and how do you enable it in Flask?
Cross-Origin Resource Sharing restricts browser requests to same-origin by default. Use Flask-CORS:
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # allow all origins (dev only)
# Production — restrict to specific origins
CORS(app, resources={
r'/api/*': {'origins': 'https://yourfrontend.com'}
})
25. How do you implement rate limiting in Flask?
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(get_remote_address, app=app,
default_limits=['200 per day', '50 per hour'])
@app.route('/api/search')
@limiter.limit('10 per minute')
def search():
...
Database
26. How do you use SQLAlchemy with Flask?
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/mydb'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
posts = db.relationship('Post', backref='author', lazy='select')
def __repr__(self):
return f'<User {self.username}>'
27. What are common SQLAlchemy query patterns?
# Read
User.query.all()
User.query.get(1) # deprecated in SQLAlchemy 2.x
db.session.get(User, 1) # SQLAlchemy 2.x style
User.query.filter_by(username='alice').first()
User.query.filter(User.age > 18).order_by(User.name).limit(10).all()
User.query.filter_by(username='alice').first_or_404()
# Create
user = User(username='alice', email='alice@example.com')
db.session.add(user)
db.session.commit()
# Update
user.email = 'newalice@example.com'
db.session.commit()
# Delete
db.session.delete(user)
db.session.commit()
# Bulk
db.session.execute(db.update(User).where(User.active == False).values(active=True))
db.session.commit()
28. What is Flask-Migrate and how do you use it?
Flask-Migrate wraps Alembic to manage database migrations with version control.
flask db init # creates migrations/ folder (once)
flask db migrate -m "add users table" # auto-detect model changes
flask db upgrade # apply migrations
flask db downgrade # revert last migration
flask db history # show migration history
29. What is the N+1 query problem in SQLAlchemy?
Loading related objects in a loop triggers N extra queries:
# BAD — N+1: 1 query for users + N queries for posts
users = User.query.all()
for user in users:
print(user.posts) # each access fires a SELECT
# GOOD — eager loading: 1 query with JOIN
users = User.query.options(db.joinedload(User.posts)).all()
for user in users:
print(user.posts) # no extra queries
Authentication
30. How do you implement session-based auth with Flask-Login?
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
login_manager = LoginManager(app)
login_manager.login_view = 'auth.login'
class User(UserMixin, db.Model):
...
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
@app.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(email=request.form['email']).first()
if user and check_password_hash(user.password, request.form['password']):
login_user(user, remember=True)
return redirect(url_for('dashboard'))
return 'Invalid credentials', 401
@app.route('/dashboard')
@login_required
def dashboard():
return f'Hello, {current_user.username}'
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
31. How do you implement JWT authentication in Flask?
from flask_jwt_extended import (
JWTManager, create_access_token, jwt_required, get_jwt_identity
)
app.config['JWT_SECRET_KEY'] = 'super-secret'
jwt = JWTManager(app)
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
if not user or not check_password_hash(user.password, data['password']):
return jsonify({'error': 'Bad credentials'}), 401
token = create_access_token(identity=user.id, expires_delta=timedelta(hours=1))
return jsonify({'access_token': token})
@app.route('/me')
@jwt_required()
def me():
user_id = get_jwt_identity()
user = db.session.get(User, user_id)
return jsonify({'id': user.id, 'email': user.email})
32. How do you hash passwords in Flask?
Use Werkzeug's (built-in to Flask) password hashing:
from werkzeug.security import generate_password_hash, check_password_hash
# Hash on registration
hashed = generate_password_hash('mypassword', method='pbkdf2:sha256')
# Verify on login
check_password_hash(hashed, 'mypassword') # True
check_password_hash(hashed, 'wrongpass') # False
Never store plain-text passwords. PBKDF2/bcrypt/scrypt are all safe choices.
Testing
33. How do you test a Flask application?
Flask provides a test client that simulates HTTP requests without starting a real server.
# conftest.py
import pytest
from app import create_app, db as _db
@pytest.fixture
def app():
app = create_app('testing')
with app.app_context():
_db.create_all()
yield app
_db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
# test_api.py
def test_get_items_empty(client):
resp = client.get('/api/items')
assert resp.status_code == 200
assert resp.get_json() == []
def test_create_item(client):
resp = client.post('/api/items',
json={'name': 'Widget'},
content_type='application/json')
assert resp.status_code == 201
data = resp.get_json()
assert data['name'] == 'Widget'
def test_get_nonexistent(client):
resp = client.get('/api/items/999')
assert resp.status_code == 404
34. How do you test authenticated routes in Flask?
def test_protected_route(client, app):
# Log in first
with client:
with client.session_transaction() as sess:
sess['user_id'] = 1 # fake session data
resp = client.get('/dashboard')
assert resp.status_code == 200
# Or via login endpoint
def test_login_and_access(client):
client.post('/login', json={'email': 'test@test.com', 'password': 'pass'})
resp = client.get('/dashboard')
assert resp.status_code == 200
35. How do you use before_request and after_request?
@app.before_request
def check_maintenance():
if app.config.get('MAINTENANCE_MODE') and request.path != '/status':
abort(503)
@app.before_request
def load_logged_in_user():
user_id = session.get('user_id')
g.user = db.session.get(User, user_id) if user_id else None
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
return response
@app.teardown_appcontext
def close_db(error):
db.session.remove()
Configuration
36. What are the different ways to configure Flask?
# 1. Direct assignment
app.config['DEBUG'] = True
# 2. From a Python object
class ProductionConfig:
DEBUG = False
SQLALCHEMY_DATABASE_URI = 'postgresql://...'
SECRET_KEY = os.environ['SECRET_KEY']
app.config.from_object(ProductionConfig)
# 3. From environment variable pointing to a file
app.config.from_envvar('APP_CONFIG') # reads path from $APP_CONFIG
# 4. From a .env file (with python-dotenv)
from dotenv import load_dotenv
load_dotenv()
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
37. How do you handle different environments (dev/staging/prod)?
# config.py
import os
class BaseConfig:
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret')
class DevelopmentConfig(BaseConfig):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
SECRET_KEY = os.environ['SECRET_KEY']
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig,
}
Performance and production
38. How do you deploy Flask in production?
Never use Flask's built-in dev server in production. Use a WSGI server:
# Gunicorn (most common)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app
# uWSGI
uwsgi --http :8000 --module wsgi:app --processes 4 --threads 2
# Hypercorn (ASGI/async Flask 2.x)
hypercorn wsgi:app --bind 0.0.0.0:8000
Typical production stack:
Client → Nginx (reverse proxy, TLS, static files) → Gunicorn → Flask app → PostgreSQL
39. How do you add caching to Flask?
from flask_caching import Cache
cache = Cache(app, config={'CACHE_TYPE': 'RedisCache',
'CACHE_REDIS_URL': 'redis://localhost:6379'})
@app.route('/expensive')
@cache.cached(timeout=300) # cache for 5 minutes
def expensive_view():
result = run_heavy_query()
return jsonify(result)
# Cache a function result with a custom key
@cache.memoize(timeout=60)
def get_user_stats(user_id):
...
# Invalidate manually
cache.delete_memoized(get_user_stats, user_id)
cache.clear()
40. How do you log in Flask?
import logging
from flask import Flask
app = Flask(__name__)
# Flask uses Python's logging module
app.logger.info('Server started')
app.logger.warning('Missing config key')
app.logger.error('Database error', exc_info=True)
# Production: structured JSON logging
import json_log_formatter
formatter = json_log_formatter.JSONFormatter()
handler = logging.StreamHandler()
handler.setFormatter(formatter)
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
Advanced topics
41. What are Flask signals and when would you use them?
Flask uses blinker to provide signals — a lightweight pub/sub mechanism for decoupled event handling.
from flask import request_started, request_finished, got_request_exception
@request_started.connect_via(app)
def on_request_started(sender, **extra):
print(f'Request started: {request.path}')
@got_request_exception.connect_via(app)
def on_exception(sender, exception, **extra):
sentry_sdk.capture_exception(exception)
Use signals to hook into Flask's lifecycle without modifying core code — ideal for monitoring, logging, and cross-cutting concerns.
42. How do you run background tasks in Flask?
Flask itself is synchronous. For background work, use:
| Tool | Best for |
|---|---|
| Celery + Redis/RabbitMQ | Long tasks, scheduled jobs, retries |
| RQ (Redis Queue) | Simpler Celery alternative |
threading.Thread |
Quick fire-and-forget (not for production) |
| APScheduler | Scheduled/recurring tasks |
# Celery example
from celery import Celery
celery = Celery(app.name, broker='redis://localhost:6379/0')
celery.conf.update(app.config)
@celery.task
def send_email_async(to, subject, body):
# send email...
pass
# In a route
@app.route('/send')
def send():
send_email_async.delay('user@example.com', 'Hi', 'Hello!')
return 'Email queued', 202
43. What is Flask-SocketIO?
Flask-SocketIO adds WebSocket support to Flask for real-time bidirectional communication.
from flask_socketio import SocketIO, emit
socketio = SocketIO(app, cors_allowed_origins='*')
@socketio.on('message')
def handle_message(data):
emit('response', {'text': f'Echo: {data["text"]}'}, broadcast=True)
if __name__ == '__main__':
socketio.run(app, debug=True)
44. What is the difference between @app.before_request and @app.before_first_request?
@app.before_request— runs before every request.@app.before_first_request— ran before the first request only (removed in Flask 2.3; usewith app.app_context()increate_app()instead orclicommands).
45. How do you serve static files in Flask?
Flask automatically serves files from the static/ folder:
# URL: /static/style.css → file: static/style.css
# In templates
url_for('static', filename='style.css') # '/static/style.css'
# Custom static folder
app = Flask(__name__, static_folder='assets', static_url_path='/assets')
In production, serve static files via Nginx instead of Flask for performance.
Anti-patterns
| Anti-pattern | Problem | Fix |
|---|---|---|
App-level app = Flask(__name__) without factory |
Hard to test, circular imports | Use create_app() factory |
| Storing secrets in source code | Security breach | Use os.environ or a secrets manager |
Using app.run() in production |
Single-threaded, no restart on crash | Use Gunicorn / uWSGI |
debug=True in production |
Exposes code in browser error pages | Always False in prod |
db.session.commit() in every route without rollback |
Silent data inconsistency | Use context managers or teardown |
Importing app into blueprints (circular import) |
Import errors | Use current_app proxy or factory pattern |
| SELECT in a loop (N+1) | Slow queries | joinedload() / subqueryload() |
session for large data |
Cookie size limit (4KB) | Server-side sessions (Flask-Session) |
Flask vs Django vs FastAPI
| Dimension | Flask | Django | FastAPI |
|---|---|---|---|
| Type | Micro-framework | Full-framework | Async API framework |
| Performance | Good (sync) | Good (sync) | Excellent (async) |
| ORM | Bring your own | Django ORM (built-in) | SQLAlchemy / SQLModel |
| Admin panel | Third-party | Built-in | Third-party |
| Auto API docs | Manual / Flask-RESTX | DRF Spectacular | Built-in (Swagger + ReDoc) |
| Request validation | marshmallow / Pydantic | DRF Serializers | Pydantic v2 (built-in) |
| Learning curve | Low | Medium | Low-Medium |
| Async support | Partial (Flask 2.x) | Partial (Django 3.1+) | First-class |
| Best for | APIs, microservices, flexibility | Full web apps, CMS | High-performance APIs, ML serving |
Common mistakes
| Mistake | Why it's wrong | Fix |
|---|---|---|
app.run(debug=True) in production |
Exposes debugger PIN and source | Set DEBUG=False via env var |
Hardcoded SECRET_KEY = 'secret' |
Sessions compromised if leaked | Use a 32-byte random value from secrets.token_hex() |
Missing if __name__ == '__main__': guard |
Module re-runs on import | Always guard app.run() |
Mutating request.args or request.form |
They're ImmutableMultiDict | Create a new dict: dict(request.args) |
Using sqlite:///db.sqlite3 in production |
No concurrent writes | Use PostgreSQL or MySQL |
| No input validation on API endpoints | Injection / crashes | Validate with marshmallow or Pydantic |
Catching Exception without re-raising |
Swallows crashes silently | Log + return 500, or use @app.errorhandler(Exception) |
| Blocking I/O without threading/async | Request queue stalls | Use Celery for slow tasks, or Flask 2.x async views |
FAQ
Q: When should I choose Flask over Django? A: Flask when you need flexibility — microservices, APIs, or a custom tech stack (e.g., MongoDB + your own auth). Django when you need rapid full-stack development with admin, ORM, and auth out of the box.
Q: Is Flask suitable for large applications? A: Yes. Use the Application Factory pattern + Blueprints to keep it modular. Companies like Pinterest and LinkedIn have used Flask at scale.
Q: What is Flask 2.x vs older Flask?
A: Flask 2.0 (2021) added native async view support, async def routes, and removed deprecated features like before_first_request. Flask 3.0 (2023) dropped Python 2 support and removed more legacy APIs.
Q: What is the difference between jsonify and json.dumps?
A: jsonify creates a Flask Response object with Content-Type: application/json. json.dumps returns a plain string — you'd still need to wrap it in make_response with the right content type.
Q: How does Flask handle concurrency?
A: By default, Flask's dev server is single-threaded. Gunicorn spawns multiple worker processes (each handling one request at a time). For I/O concurrency, use gevent workers or Flask's native async views with an ASGI server.
Q: What is flask shell used for?
A: It starts a Python REPL with the application context pre-loaded (so you can run User.query.all() without any setup). Add objects to it via @app.shell_context_processor.