If you are preparing for a Python web developer interview, Flask is one of the most commonly tested frameworks. Whether the role involves building microservices, REST APIs, or full web applications, interviewers expect you to demonstrate not just familiarity with Flask’s API, but a deeper understanding of its design philosophy and the trade-offs it makes compared to heavier frameworks like Django.
This guide covers the questions that actually come up in technical interviews, organized by category and difficulty. Each answer includes the reasoning behind it, production-quality code examples, and notes on what the interviewer is really evaluating. If you can speak confidently to these topics, you will be well-positioned for mid-level to senior Flask roles.
Flask is a micro web framework for Python built on top of Werkzeug (a WSGI toolkit) and Jinja2 (a template engine). The term “micro” does not mean Flask lacks capability — it means Flask does not impose decisions on you. There is no built-in ORM, no form validation library, and no specific project layout required out of the box.
You would choose Flask when you need fine-grained control over your application’s architecture, when you are building a small-to-medium service or API, or when your team prefers to pick and compose libraries rather than accept a monolithic framework’s opinions. Flask is also the go-to choice for microservices because of its minimal footprint and fast startup time.
Why interviewers ask this: They want to see that you understand Flask’s philosophy, not just its syntax. A strong answer shows you can reason about architectural trade-offs.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, Flask!"
if __name__ == '__main__':
app.run(debug=True)
The Flask(__name__) call tells Flask where to find templates and static files relative to the module. The @app.route decorator binds a URL path to a Python function. When you run this file directly, app.run(debug=True) starts the development server with the interactive debugger and auto-reloader enabled.
Why interviewers ask this: It is a baseline check. They are watching for whether you include the if __name__ guard and whether you know what the __name__ argument does.
The application factory pattern defers the creation of the Flask app object to a function rather than creating it at module level. This solves several real-world problems: it allows you to create multiple instances with different configurations (critical for testing), it avoids circular import issues, and it gives you a clean place to register extensions, blueprints, and error handlers.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
migrate.init_app(app, db)
from .main import main_bp
app.register_blueprint(main_bp)
from .api import api_bp
app.register_blueprint(api_bp, url_prefix='/api/v1')
return app
Why interviewers ask this: This separates junior from senior candidates. If you only know the single-file style, you have likely not worked on production Flask applications. Interviewers want to see that you understand testability and modularity.
Blueprints let you organize a Flask application into discrete components, each with its own routes, templates, static files, and error handlers. They are registered on the application object, not instantiated as apps themselves. This is Flask’s answer to modularization.
from flask import Blueprint
auth_bp = Blueprint('auth', __name__, template_folder='templates')
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# login logic here
return render_template('auth/login.html')
@auth_bp.route('/logout')
def logout():
# logout logic here
return redirect(url_for('main.index'))
In your factory function, you register it:
app.register_blueprint(auth_bp, url_prefix='/auth')
Use blueprints when your application has logically distinct areas (authentication, admin panel, public API, etc.) or when multiple developers are working on different features simultaneously. Blueprints also enable you to package reusable functionality that can be shared across projects.
Why interviewers ask this: They are testing whether you have experience structuring non-trivial applications. Knowing blueprints signals production experience.
Flask uses two context stacks to manage state without passing objects explicitly through every function call:
current_app, g): Active when the app is handling a request or when you manually push a context. The current_app proxy gives you access to the app’s configuration and extensions. The g object is a per-request namespace for storing data (like a database connection) that should be available throughout the request lifecycle.request, session): Created when Flask receives an HTTP request and torn down after the response is sent. The request proxy gives you access to headers, form data, query parameters, and JSON payloads. The session proxy provides access to the user’s session data.from flask import current_app, g, request
@app.route('/example')
def example():
# request context is active here
user_agent = request.headers.get('User-Agent')
# application context is also active
debug_mode = current_app.config['DEBUG']
# g is per-request storage
g.db = get_db_connection()
return f"Debug: {debug_mode}, UA: {user_agent}"
A common pitfall is trying to access current_app or request outside of a request. In CLI commands or background tasks, you need to manually push an application context using with app.app_context():.
Why interviewers ask this: Understanding contexts is one of the most reliable indicators of Flask depth. Developers who have only built toy apps will struggle with this question.
Flask’s power comes from its extension ecosystem. Extensions follow a convention: they provide an init_app() method for deferred initialization (compatible with the factory pattern) and are typically prefixed with Flask-. The most important ones to know are:
When evaluating an extension, check that it supports init_app(), is actively maintained, and does not tightly couple you to a specific implementation.
Why interviewers ask this: They want to know if you can build a production application by selecting the right tools, and whether you understand deferred initialization.
A route maps a URL pattern to a Python function (called a view function). Flask uses Werkzeug’s routing system under the hood, which supports static paths, variable rules with type converters, and HTTP method filtering.
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
Flask’s built-in URL converters include string (default), int, float, path (like string but accepts slashes), and uuid. You can also create custom converters for specialized URL patterns.
Why interviewers ask this: Routing is foundational. They want to see that you know about type converters, HTTP method restrictions, and the 404 shortcut methods.
The primary HTTP methods are:
@app.route('/articles/<int:article_id>', methods=['GET', 'PUT', 'PATCH', 'DELETE'])
def article(article_id):
article = Article.query.get_or_404(article_id)
if request.method == 'GET':
return jsonify(article.to_dict())
elif request.method == 'PUT':
data = request.get_json()
article.title = data['title']
article.body = data['body']
db.session.commit()
return jsonify(article.to_dict())
elif request.method == 'PATCH':
data = request.get_json()
if 'title' in data:
article.title = data['title']
if 'body' in data:
article.body = data['body']
db.session.commit()
return jsonify(article.to_dict())
elif request.method == 'DELETE':
db.session.delete(article)
db.session.commit()
return '', 204
Why interviewers ask this: They are checking whether you understand REST semantics (idempotency, safety) beyond just knowing the method names.
from flask import redirect, url_for
@app.route('/old-dashboard')
def old_dashboard():
return redirect(url_for('new_dashboard'), code=301)
@app.route('/dashboard')
def new_dashboard():
return "Welcome to the new dashboard"
Always use url_for() instead of hardcoding URLs. It builds URLs from the endpoint name, which means your redirects will not break if you rename a route’s URL pattern. Use 301 for permanent redirects (SEO-friendly) and 302 (the default) for temporary redirects.
Why interviewers ask this: Using url_for() instead of a string literal is a signal that you understand maintainable Flask development.
Jinja2 is Flask’s default template engine. It lets you embed Python-like expressions in HTML while enforcing a separation between logic and presentation. Templates are stored in a templates/ directory by default.
# In your view
from flask import render_template
@app.route('/profile/<username>')
def profile(username):
user = User.query.filter_by(username=username).first_or_404()
posts = user.posts.order_by(Post.created_at.desc()).all()
return render_template('profile.html', user=user, posts=posts)
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head><title>{% block title %}My App{% endblock %}</title></head>
<body>
<nav>{% include 'nav.html' %}</nav>
{% block content %}{% endblock %}
</body>
</html>
<!-- templates/profile.html -->
{% extends 'base.html' %}
{% block title %}{{ user.username }}'s Profile{% endblock %}
{% block content %}
<h1>{{ user.username }}</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.body | truncate(200) }}</p>
<small>{{ post.created_at | strftime('%B %d, %Y') }}</small>
</article>
{% else %}
<p>No posts yet.</p>
{% endfor %}
{% endblock %}
Key Jinja2 features include template inheritance (extends/block), includes, macros (reusable template functions), filters (like truncate, safe, escape), and automatic HTML escaping to prevent XSS attacks.
Why interviewers ask this: Template inheritance and auto-escaping are essential to building secure, maintainable web apps. Knowing about {% else %} on for-loops shows deeper Jinja2 knowledge.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/mydb'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress deprecation warning
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
'pool_size': 10,
'pool_recycle': 3600,
}
db.init_app(app)
return app
Always set SQLALCHEMY_TRACK_MODIFICATIONS to False unless you specifically need the event system — it consumes extra memory. In production, configure connection pooling parameters to prevent database connection exhaustion.
Why interviewers ask this: Setting SQLALCHEMY_TRACK_MODIFICATIONS = False and knowing about connection pooling separates production experience from tutorial-level knowledge.
from datetime import datetime
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __repr__(self):
return f'<User {self.username}>'
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
body = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
def __repr__(self):
return f'<Post {self.title}>'
Notice the use of lazy='dynamic' on the relationship, which returns a query object instead of loading all posts eagerly. This is important when a user could have thousands of posts. Also note the explicit __tablename__, indexes on frequently queried columns, and nullable=False constraints to enforce data integrity at the database level.
Why interviewers ask this: Modeling relationships correctly, understanding lazy loading strategies, and adding proper indexes demonstrates database design competence.
Flask-Migrate wraps Alembic, the database migration tool for SQLAlchemy. It tracks changes to your models and generates migration scripts that can be applied (or rolled back) to keep the database schema in sync with your code.
# Terminal commands # Initialize the migration repository (run once) # flask db init # Generate a migration after changing models # flask db migrate -m "add posts table" # Apply the migration to the database # flask db upgrade # Roll back the last migration # flask db downgrade
Always review the auto-generated migration script before running upgrade. Alembic cannot detect all changes (for example, renaming a column will be detected as a drop and create, which destroys data). In those cases, you need to manually edit the migration.
Why interviewers ask this: Database migrations are critical in production. Knowing that you need to review auto-generated scripts and understanding rollback shows operational maturity.
There are two main approaches depending on your application type:
Session-based authentication (for traditional web apps): Use Flask-Login, which manages user sessions and provides decorators like @login_required.
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@auth_bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
if user and check_password_hash(user.password_hash, data['password']):
login_user(user, remember=data.get('remember', False))
return jsonify({'message': 'Logged in successfully'})
return jsonify({'error': 'Invalid credentials'}), 401
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
return jsonify({'message': 'Logged out successfully'})
Token-based authentication (for APIs): Use Flask-JWT-Extended for stateless JWT authentication.
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
jwt = JWTManager(app)
@app.route('/api/login', methods=['POST'])
def api_login():
data = request.get_json()
user = User.query.filter_by(username=data['username']).first()
if user and check_password_hash(user.password_hash, data['password']):
access_token = create_access_token(identity=user.id)
return jsonify(access_token=access_token)
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/api/protected')
@jwt_required()
def protected():
current_user_id = get_jwt_identity()
user = User.query.get(current_user_id)
return jsonify(logged_in_as=user.username)
Why interviewers ask this: They are testing whether you know when to use sessions vs. tokens, and whether you handle passwords securely (hashing, not storing plaintext).
werkzeug.security.generate_password_hash(). Never store plaintext passwords.SECRET_KEY from environment variables, never hardcode it.request.get_json(force=False) and validate schemas with libraries like Marshmallow or Pydantic.X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy.debug=True in production — it exposes an interactive debugger that allows arbitrary code execution.import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SESSION_COOKIE_SECURE'] = True # Only send cookies over HTTPS
app.config['SESSION_COOKIE_HTTPONLY'] = True # Prevent JavaScript access to session cookie
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF mitigation
Why interviewers ask this: Security awareness is non-negotiable for senior developers. They want to know you will not ship an application with the debug console open to the internet.
You can build APIs with plain Flask or use Flask-RESTful/Flask-RESTX for more structure. Here is a clean pattern using plain Flask with class-based views:
from flask import Flask, jsonify, request, abort
from flask.views import MethodView
app = Flask(__name__)
class UserAPI(MethodView):
def get(self, user_id=None):
if user_id is None:
# List all users
users = User.query.all()
return jsonify([u.to_dict() for u in users])
# Get single user
user = User.query.get_or_404(user_id)
return jsonify(user.to_dict())
def post(self):
data = request.get_json()
if not data or 'username' not in data:
abort(400, description='Username is required')
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify(user.to_dict()), 201
def put(self, user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
user.username = data['username']
user.email = data['email']
db.session.commit()
return jsonify(user.to_dict())
def delete(self, user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204
# Register the view
user_view = UserAPI.as_view('user_api')
app.add_url_rule('/api/users', defaults={'user_id': None}, view_func=user_view, methods=['GET'])
app.add_url_rule('/api/users', view_func=user_view, methods=['POST'])
app.add_url_rule('/api/users/<int:user_id>', view_func=user_view, methods=['GET', 'PUT', 'DELETE'])
Why interviewers ask this: They want to see if you can design clean API endpoints, return proper HTTP status codes, and handle missing resources gracefully.
Cross-Origin Resource Sharing (CORS) is required when your frontend and backend are on different domains. Use Flask-CORS:
from flask_cors import CORS
app = Flask(__name__)
# Allow all origins (development only)
CORS(app)
# Production: restrict to specific origins
CORS(app, resources={
r"/api/*": {
"origins": ["https://yourfrontend.com", "https://admin.yourfrontend.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"]
}
})
Never use a wildcard (*) for origins in production. Be explicit about which domains, methods, and headers are allowed.
Why interviewers ask this: CORS issues are one of the most common problems when building SPAs that talk to Flask APIs. Knowing how to configure it properly (and securely) is practical knowledge.
Flask provides the @app.errorhandler decorator for registering handlers for specific HTTP status codes or exception types. In production, you should handle at least 404, 500, and any custom application exceptions.
from flask import Flask, render_template, jsonify, request
app = Flask(__name__)
class APIError(Exception):
"""Custom exception for API errors."""
def __init__(self, message, status_code=400):
self.message = message
self.status_code = status_code
@app.errorhandler(APIError)
def handle_api_error(error):
return jsonify({'error': error.message}), error.status_code
@app.errorhandler(404)
def not_found(error):
if request.path.startswith('/api/'):
return jsonify({'error': 'Resource not found'}), 404
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback() # Roll back any failed transactions
if request.path.startswith('/api/'):
return jsonify({'error': 'Internal server error'}), 500
return render_template('errors/500.html'), 500
# Usage in a view
@app.route('/api/items/<int:item_id>')
def get_item(item_id):
item = Item.query.get(item_id)
if not item:
raise APIError('Item not found', 404)
return jsonify(item.to_dict())
Notice how the 500 handler calls db.session.rollback() to clean up any failed database transaction. Also note the pattern of returning JSON for API routes and HTML for browser routes — this is essential for applications that serve both.
Why interviewers ask this: Error handling reveals production mindset. Rolling back database sessions on 500 errors and distinguishing API vs. browser responses shows real-world experience.
Flask provides four hooks that execute at different points in the request lifecycle:
before_first_request — Runs once before the very first request (removed in Flask 2.3, use app.startup or initialization in the factory).before_request — Runs before each request. If it returns a response, the view function is skipped.after_request — Runs after each request. Receives and must return the response object.teardown_request — Runs after the response is sent, even if an exception occurred. Used for cleanup.import time
from flask import g, request
@app.before_request
def before_request_func():
g.start_time = time.time()
# Example: require API key for all /api routes
if request.path.startswith('/api/') and request.endpoint != 'api.login':
api_key = request.headers.get('X-API-Key')
if not api_key or not is_valid_api_key(api_key):
return jsonify({'error': 'Invalid or missing API key'}), 401
@app.after_request
def after_request_func(response):
# Log request duration
duration = time.time() - g.start_time
app.logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
# Add security headers
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
return response
@app.teardown_request
def teardown_request_func(exception):
# Close database connection if stored on g
db_conn = g.pop('db_conn', None)
if db_conn is not None:
db_conn.close()
For true WSGI middleware (which wraps the entire app), you can use Werkzeug’s ProxyFix or write your own class that implements the WSGI interface.
Why interviewers ask this: Request hooks are how you implement cross-cutting concerns like logging, authentication gates, and performance monitoring. This question tests architectural thinking.
The standard approach is to define configuration classes and select one based on an environment variable:
import os
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY', 'fallback-dev-key')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
WTF_CSRF_ENABLED = False # Disable CSRF for testing
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
SESSION_COOKIE_SECURE = True
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
# In your factory
def create_app(config_name=None):
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'default')
app = Flask(__name__)
app.config.from_object(config[config_name])
# Override with instance-specific config (not in version control)
app.config.from_pyfile('instance/config.py', silent=True)
# Override with environment variables
app.config.from_prefixed_env() # Flask 2.2+: reads FLASK_* env vars
return app
For sensitive values (database passwords, API keys), always use environment variables. Never commit secrets to version control. Use tools like python-dotenv to load .env files in development.
Why interviewers ask this: Every production application needs environment-specific configuration. This question tests whether you can structure an app for real deployment workflows.
Flask provides a test client that simulates HTTP requests without running a server. Combined with pytest and the application factory pattern, you get clean, isolated tests:
import pytest
from myapp import create_app, db
@pytest.fixture
def app():
app = create_app('testing')
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def runner(app):
return app.test_cli_runner()
def test_home_page(client):
response = client.get('/')
assert response.status_code == 200
assert b'Welcome' in response.data
def test_create_user(client):
response = client.post('/api/users', json={
'username': 'testuser',
'email': 'test@example.com',
'password': 'securepassword'
})
assert response.status_code == 201
data = response.get_json()
assert data['username'] == 'testuser'
def test_get_nonexistent_user(client):
response = client.get('/api/users/9999')
assert response.status_code == 404
def test_login_required(client):
response = client.get('/api/protected')
assert response.status_code == 401
Key testing practices: use an in-memory SQLite database for speed, create and drop tables for each test (or use transactions with rollback), and test both success and failure cases. Use client.get(), client.post(json=...), client.put(), etc. to simulate requests.
Why interviewers ask this: Writing tests is a hard requirement for senior roles. They want to see that you use fixtures, test isolation, and cover edge cases — not just the happy path.
Flask’s built-in server is for development only. For production, you need a proper WSGI server behind a reverse proxy:
# wsgi.py
from myapp import create_app
app = create_app('production')
# Run with Gunicorn # gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app # Or with uWSGI # uwsgi --http :8000 --wsgi-file wsgi.py --callable app --processes 4 --threads 2
A typical production stack looks like:
# Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]
Important production considerations: set DEBUG = False, use environment variables for secrets, configure proper logging, set up health check endpoints, and use a process manager like systemd or Docker to restart crashed workers.
Why interviewers ask this: Deployment is where many developers stumble. Knowing the Nginx + Gunicorn stack and being able to write a Dockerfile shows you can ship code, not just write it.
| Criteria | Flask | Django |
|---|---|---|
| Philosophy | Micro-framework, pick your own tools | Batteries-included, convention over configuration |
| ORM | None built-in (typically SQLAlchemy) | Built-in Django ORM |
| Admin Panel | None (use Flask-Admin if needed) | Built-in, production-ready |
| Best For | APIs, microservices, small-to-medium apps | Content sites, e-commerce, rapid prototyping |
| Learning Curve | Lower initially, higher for large apps | Higher initially, lower for large apps |
| Flexibility | Maximum flexibility in architecture | Opinionated structure |
Choose Flask when you want full control over your stack, you are building a microservice or API, your team is experienced and wants to pick best-of-breed libraries, or you need to keep the dependency footprint small.
Choose Django when you need an admin interface out of the box, you are building a content-heavy site, you want built-in authentication/authorization/ORM without assembling pieces, or your team benefits from Django’s strong conventions.
Why interviewers ask this: This is not about which framework is “better.” They want to see that you can evaluate tools based on project requirements rather than personal preference.
Flask-WTF integrates WTForms with Flask and provides CSRF protection, file upload handling, and reCAPTCHA support.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[
DataRequired(),
Length(min=3, max=80)
])
email = StringField('Email', validators=[
DataRequired(),
Email()
])
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=8)
])
confirm_password = PasswordField('Confirm Password', validators=[
DataRequired(),
EqualTo('password', message='Passwords must match')
])
accept_tos = BooleanField('I accept the Terms of Service', validators=[DataRequired()])
submit = SubmitField('Register')
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(
username=form.username.data,
email=form.email.data,
password_hash=generate_password_hash(form.password.data)
)
db.session.add(user)
db.session.commit()
flash('Registration successful!', 'success')
return redirect(url_for('auth.login'))
return render_template('register.html', form=form)
validate_on_submit() checks both that the request method is POST and that all validators pass. CSRF protection is enabled automatically as long as you set SECRET_KEY and include {{ form.hidden_tag() }} in your template.
Why interviewers ask this: Form handling and validation are fundamental to web applications. Knowing about CSRF protection and proper validation chains shows security awareness.
from flask_caching import Cache
cache = Cache()
def create_app():
app = Flask(__name__)
app.config['CACHE_TYPE'] = 'RedisCache'
app.config['CACHE_REDIS_URL'] = 'redis://localhost:6379/0'
app.config['CACHE_DEFAULT_TIMEOUT'] = 300
cache.init_app(app)
return app
# Cache an entire view response
@app.route('/api/stats')
@cache.cached(timeout=120, key_prefix='api_stats')
def get_stats():
# Expensive database aggregation
stats = compute_expensive_stats()
return jsonify(stats)
# Cache a function result with dynamic keys
@cache.memoize(timeout=60)
def get_user_profile(user_id):
return User.query.get(user_id)
# Invalidate cache when data changes
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
# ... update logic ...
cache.delete_memoized(get_user_profile, user_id)
return jsonify(user.to_dict())
Use @cache.cached() for view-level caching and @cache.memoize() for function-level caching with argument-based keys. In production, use Redis or Memcached as the cache backend, not the simple in-memory cache.
Why interviewers ask this: Caching is critical for performance at scale. They want to see that you understand cache invalidation strategies and the difference between view-level and function-level caching.
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, join_room, leave_room
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('connect')
def handle_connect():
emit('server_message', {'data': 'Connected successfully'})
@socketio.on('join')
def handle_join(data):
room = data['room']
join_room(room)
emit('server_message', {'data': f'Joined room: {room}'}, room=room)
@socketio.on('chat_message')
def handle_message(data):
room = data.get('room', 'general')
emit('new_message', {
'user': data['user'],
'message': data['message']
}, room=room, include_self=False)
if __name__ == '__main__':
socketio.run(app, debug=True)
Flask-SocketIO supports rooms for group messaging, namespaces for logical separation, and can use Redis as a message queue for multi-process deployments. For production, you need an async-capable worker like eventlet or gevent.
Why interviewers ask this: Real-time features are increasingly common. Understanding rooms, namespaces, and the production requirements for WebSocket deployments shows full-stack capability.
201 for creation and 204 for deletion.app object, the interviewer will wonder whether you have built anything beyond a tutorial.app.run(). Gunicorn, Nginx, Docker, environment variables, and proper logging are all part of shipping a Flask application.