{"id":2014,"date":"2026-07-22T11:00:00","date_gmt":"2026-07-22T05:30:00","guid":{"rendered":"https:\/\/lastroundai.com\/blog\/?post_type=iq&#038;p=2014"},"modified":"2026-07-21T20:30:23","modified_gmt":"2026-07-21T15:00:23","slug":"flask","status":"publish","type":"iq","link":"https:\/\/lastroundai.com\/interview-questions\/flask","title":{"rendered":"Flask Interview Questions (2026): Most Asked, With Answers"},"content":{"rendered":"<p>A candidate at a Series B logistics startup once got asked to explain why a database connection opened in one view kept showing up, unclosed, three requests later under load. He&#8217;d used Flask for two years and had never once had to think about it, because his team&#8217;s boilerplate handled it for them. The real answer lives in one object most tutorials skip past: the application context, and specifically what happens on <code>teardown_appcontext<\/code> when a worker is juggling more than one request. Flask sits at 13.2 percent usage among professional developers in the 2025 Stack Overflow survey, behind the newer FastAPI at 15.1 percent after a five-point jump this year (<a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"noopener noreferrer\">Stack Overflow, 2025<\/a>). It&#8217;s no longer the only Python web option candidates reach for, which means interviewers lean harder on the parts of Flask that are genuinely Flask-specific rather than generic REST trivia.<\/p>\n<p>Here&#8217;s a pattern worth naming: most Flask interview prep repeats the same five questions about decorators and routes, then stops right before the material that actually separates candidates, contexts, blueprints, and what happens to global-looking objects like <code>g<\/code> and <code>current_app<\/code> once a WSGI server spins up more than one worker. You can write a working CRUD app for a year using <code>@app.route<\/code> and never touch <code>app_context()<\/code> directly. You can&#8217;t debug a production incident involving a stale database connection, a leaked secret key, or a blueprint that silently swallowed a 404 without understanding it.<\/p>\n<p>This page covers Flask interview questions across nine areas: WSGI fundamentals, routing and request data, application and request context, Jinja2 templating, sessions and security, blueprints and the application factory pattern, extensions and the data layer, testing and debugging, and deployment with async support. Code examples are Python throughout, targeting current Flask 3.1.x behavior per the <a href=\"https:\/\/flask.palletsprojects.com\/en\/stable\/\" target=\"_blank\" rel=\"noopener noreferrer\">official Flask documentation<\/a>.<\/p>\n<div class=\"iq-stats not-prose\"><div class=\"iq-stat\"><span class=\"iq-stat__value\">53<\/span><span class=\"iq-stat__label\">Questions<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">Application Context<\/span><span class=\"iq-stat__label\">Core Concept<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">13.2%<\/span><span class=\"iq-stat__label\">2025 Pro Dev Usage<\/span><\/div><div class=\"iq-stat\"><span class=\"iq-stat__value\">15.1%<\/span><span class=\"iq-stat__label\">FastAPI 2025 Usage<\/span><\/div><\/div>\n<h2>Flask fundamentals and WSGI<\/h2>\n<p>Every loop starts here, and a wobbly answer on what Flask actually is under the hood sets a low ceiling for the rest of the conversation.<\/p>\n<div class=\"iq-dsec iq-dsec--easy\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"easy\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Easy questions<\/h2><span class=\"iq-dsec__n\">14<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is Flask, and how is it different from Django?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Fundamentals<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask is a WSGI micro-framework: it gives you routing, request and response objects, and Jinja2 templating, then gets out of the way. It doesn&#8217;t ship an ORM, an admin panel, or a form library. Django is a full-stack framework with all of that built in and a stronger set of opinions about how a project is laid out.<\/p>\n<p>The practical trade-off shows up in every real project. Flask apps end up composed from extensions, Flask-SQLAlchemy for the database, Flask-Migrate for schema changes, Flask-Login for auth, so the framework itself stays small but the dependency list grows. Django gives you more out of the box at the cost of more structure to learn up front. Neither is objectively better; the choice tends to come down to whether the team wants Django&#8217;s conventions or wants to pick each piece itself.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is WSGI, and where does Flask sit in that stack?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">WSGI<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>WSGI (Web Server Gateway Interface) is the standard Python interface between a web server and a Python web application. It defines a simple callable, an app object that takes an environ dict and a start_response function and returns an iterable of bytes. Any WSGI server, gunicorn, uWSGI, the Flask dev server, can talk to any WSGI application.<\/p>\n<p>Flask&#8217;s <code>Flask<\/code> instance is a WSGI application. That&#8217;s the whole reason `app = Flask(__name__)` can be run by gunicorn in production: `app` satisfies the WSGI callable contract underneath all the routing and decorators Flask adds on top.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why shouldn&#039;t you run flask run&#039;s development server in production?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Deployment<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The built-in dev server is single-threaded by default, isn&#8217;t hardened against malformed requests, and ships with the interactive debugger available when debug mode is on, which lets anyone who reaches it execute arbitrary Python on your server. Flask&#8217;s own docs are direct about this: it&#8217;s for local development, not for anything internet-facing.<\/p>\n<p>In production you put a real WSGI server, gunicorn or uWSGI, in front of the app, usually behind nginx handling TLS and static files. The Flask app object doesn&#8217;t change; only what&#8217;s serving it does.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do variable rules and converters work in a Flask route?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Routing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>You mark a dynamic segment with angle brackets, `<username>`, and Flask passes it to the view function as a keyword argument. Adding a converter before the name, `<int:post_id>`, tells Flask to both validate and coerce the segment to that type before the view ever sees it.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/user\/&lt;username&gt;\u2019)\n\ndef show_user_profile(username):\n\n    return f\u2019User {username}\u2019\n@app.route(\u2018\/post\/&lt;int:post_id&gt;\u2019)\n\ndef show_post(post_id):\n\n    return f\u2019Post {post_id}\u2019  # post_id is already an int here\n<\/code><\/pre><\/div><\/p>\n<p>The built-in converters are string (default, no slashes), int, float, path (like string but allows slashes), and uuid. Requesting `\/post\/abc` against the int converter returns a 404 automatically, before your view code runs.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference in behavior between a route defined with a trailing slash and one without?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Routing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A route defined as `\/projects\/` treats the trailing slash as canonical, like a folder. Requesting `\/projects` without the slash gets a redirect to `\/projects\/`. A route defined as `\/about`, with no trailing slash, treats that as canonical like a filename, and requesting `\/about\/` returns a 404 instead of redirecting.<\/p>\n<p>This trips people up because it&#8217;s asymmetric: dropping a slash from a slash-based route redirects, but adding one to a no-slash route doesn&#8217;t. It&#8217;s a small detail, but it explains a surprising number of &#8220;why does this URL 404 sometimes&#8221; bug reports.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you restrict a route to specific HTTP methods, and what does Flask add automatically?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Routing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Pass a `methods` list to `@app.route`, or use the method-specific shortcuts `@app.get()` and `@app.post()` added for readability. A route only answers `GET` by default if you don&#8217;t specify anything.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/login\u2019, methods=[\u2019GET\u2019, \u2018POST\u2019])\n\ndef login():\n\n    if request.method == \u2018POST\u2019:\n\n        return do_the_login()\n\n    return show_the_login_form()\n<\/code><\/pre><\/div><\/p>\n<p>If `GET` is in the list, Flask automatically adds support for `HEAD` and answers it per the HTTP spec without you writing a handler. `OPTIONS` is also implemented automatically for every route, which is why a stray `OPTIONS \/login` returning 200 in a test isn&#8217;t a bug, it&#8217;s Flask doing its job.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is the g object used for, and how long does its data live?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>g<\/code> is a namespace object Flask gives you to stash data for the duration of a single application context, most commonly the lifetime of one request. It&#8217;s meant for per-request scratch space, a cached database connection, the currently authenticated user resolved once and reused, not for anything that needs to persist between requests.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask import g\ndef get_db():\n\n    if \u2018db\u2019 not in g:\n\n        g.db = connect_to_database()\n\n    return g.db\n@app.teardown_appcontext\n\ndef teardown_db(exception):\n\n    db = g.pop(\u2018db\u2019, None)\n\n    if db is not None:\n\n        db.close()\n<\/code><\/pre><\/div><\/p>\n<p>The &#8220;g&#8221; stands for global, but only global within that one context. Once the context ends, whatever was on `g` is gone. Reach for `session` or a real database if you need data to survive across requests.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Where does Flask look for templates, and how does render_template find them?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jinja2<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask looks in a folder named `templates` next to your application module or package. Call `render_template(&#8216;hello.html&#8217;, **context)` and Flask hands the name and any keyword arguments to Jinja2, which loads the file from that search path and renders it with the given variables available.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask import render_template\n@app.route(\u2018\/hello\/&lt;name&gt;\u2019)\n\ndef hello(name):\n\n    return render_template(\u2018hello.html\u2019, person=name)\n<\/code><\/pre><\/div><\/p>\n<p>Inside the template you also automatically get access to `config`, `request`, `session`, and `g`, plus the `url_for()` and `get_flashed_messages()` functions, without passing any of them in explicitly.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Jinja2 template inheritance work with blocks and extends?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jinja2<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A base template defines named blocks, `{% block content %}{% endblock %}`, for the sections that will vary. A child template declares `{% extends &#8220;base.html&#8221; %}` and then overrides just the blocks it wants to change, inheriting everything else, header, nav, footer, from the parent unchanged.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">html<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-html\">\n\n{# base.html #}\n\n&lt;html&gt;&lt;body&gt;{% block content %}{% endblock %}&lt;\/body&gt;&lt;\/html&gt;\n{# page.html #}\n\n{% extends \u201cbase.html\u201d %}\n\n{% block content %}&lt;p&gt;Hello!&lt;\/p&gt;{% endblock %}\n<\/code><\/pre><\/div><\/p>\n<p>This is the standard way to keep a shared layout, header, navigation, footer, in one place instead of copy-pasting it into every page template.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you read and write cookies directly in a Flask view?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Cookies<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Read cookies from `request.cookies`, a dict-like object; set them on a `Response` with `.set_cookie()`, since cookies are attached to the outgoing response, not to the view function&#8217;s return value directly.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/\u2019)\n\ndef index():\n\n    username = request.cookies.get(\u2018username\u2019)  # .get() avoids a KeyError\n\n    resp = make_response(render_template(\u2018index.html\u2019))\n\n    resp.set_cookie(\u2018username\u2019, \u2018ada\u2019)\n\n    return resp\n<\/code><\/pre><\/div><\/p>\n<p>If you want anything beyond a plain unsigned value, Flask&#8217;s own `session` object already handles signing on top of cookies, so most apps use raw cookies only for genuinely non-sensitive preferences.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between session and g in terms of lifetime and use?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sessions<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>session<\/code> persists across multiple requests from the same client, since it&#8217;s stored in a cookie the browser sends back every time. <code>g<\/code> only lives for a single application context, typically one request, and is gone by the time the next request starts.<\/p>\n<p>Use `session` for anything that needs to survive between page loads, a logged-in user&#8217;s identity. Use `g` for scratch data you only need within the current request, like a lazily created database connection you don&#8217;t want to reopen three times in the same view.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is a Blueprint, and why reach for one instead of one large app.py?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Blueprints<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A Blueprint is a recorded set of operations, routes, templates, static files, error handlers, that gets applied to a real application when you call `app.register_blueprint()`. It behaves similarly to an app object but isn&#8217;t one itself; it&#8217;s a plan for how to extend an app, not something that can run on its own.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask import Blueprint\nadmin_bp = Blueprint(\u2018admin\u2019, __name__, url_prefix=\u2019\/admin\u2019)\n@admin_bp.route(\u2018\/dashboard\u2019)\n\ndef dashboard():\n\n    return \u2018Admin dashboard\u2019\napp.register_blueprint(admin_bp)\n<\/code><\/pre><\/div><\/p>\n<p>The payoff is modularity: a larger app can be split into blueprints per feature area, each with its own routes and templates, registered onto one application object at startup, instead of every route living in a single sprawling file.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does Flask-SQLAlchemy give you that raw SQLAlchemy setup doesn&#039;t?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Flask-SQLAlchemy<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask-SQLAlchemy wires SQLAlchemy&#8217;s engine and session lifecycle into Flask&#8217;s application context automatically, so you get a properly scoped session per request without configuring that plumbing by hand, plus config keys like `SQLALCHEMY_DATABASE_URI` that read naturally from `app.config`.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask_sqlalchemy import SQLAlchemy\ndb = SQLAlchemy()\nclass User(db.Model):\n\n    id = db.Column(db.Integer, primary_key=True)\n\n    name = db.Column(db.String(80))\n<\/code><\/pre><\/div><\/p>\n<p>You still get the full underlying SQLAlchemy query API, this isn&#8217;t a separate simplified ORM, it&#8217;s a thin Flask-aware wrapper around the real thing.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you test a Flask view without spinning up a real server?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--easy\">Easy<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask&#8217;s `app.test_client()` gives you an object that simulates HTTP requests directly against the app in-process, no socket, no running server, so tests are fast and don&#8217;t depend on a port being free.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef test_index():\n\n    client = app.test_client()\n\n    response = client.get(\u2018\/\u2019)\n\n    assert response.status_code == 200\n\n    assert b\u2019Hello\u2019 in response.data\n<\/code><\/pre><\/div><\/p>\n<p>This is the standard entry point for almost all Flask test suites, and it works the same whether the view returns HTML, JSON, or a redirect.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--medium\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"medium\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Medium questions<\/h2><span class=\"iq-dsec__n\">27<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What is Werkzeug, and why does Flask depend on it directly?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Werkzeug<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Werkzeug is the WSGI utility library Flask is built on. It provides the actual Request and Response objects, the URL routing system (the Map and Rule classes that power <code>@app.route<\/code>), the development server, and the interactive debugger. Flask is, in large part, a thin and opinionated layer over Werkzeug plus Jinja2 for templates.<\/p>\n<p>Knowing this matters practically because a lot of what looks like &#8220;Flask magic&#8221; is actually Werkzeug: <code>secure_filename()<\/code>, the <code>ProxyFix<\/code> middleware, and the converter types in route rules (`int`, `float`, `path`, `uuid`) all live in Werkzeug and get imported from there, not from `flask` itself.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does request.args differ from request.form, and when would you use each?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Request Object<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>request.args<\/code> holds the query string parameters from the URL, `?key=value`, and is populated regardless of HTTP method. <code>request.form<\/code> holds data submitted in the body of a POST or PUT request with a form-encoded or multipart content type, it&#8217;s empty for a GET request and empty for a JSON body too.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/search\u2019)\n\ndef search():\n\n    q = request.args.get(\u2018q\u2019, \u201d)  # from ?q=\u2026\n@app.route(\u2018\/login\u2019, methods=[\u2019POST\u2019])\n\ndef login():\n\n    username = request.form[\u2019username\u2019]  # from the submitted form body\n<\/code><\/pre><\/div><\/p>\n<p>For a JSON request body you&#8217;d use <code>request.get_json()<\/code> instead of either. Mixing these up is a common source of &#8220;the field is always empty&#8221; bugs, usually because someone read a query param off `request.form` or a POST body off `request.args`.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What happens when you try request.form[&#039;field&#039;] and the field wasn&#039;t submitted?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Request Object<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask&#8217;s `request.form` is a `MultiDict`, and indexing a missing key raises a `KeyError` just like a plain dict would. If that error propagates out of your view unhandled, Flask converts it into a 400 Bad Request response automatically, rather than a 500.<\/p>\n<p>That&#8217;s Flask being forgiving on your behalf, but it&#8217;s still worth using `request.form.get(&#8216;field&#8217;, default)` deliberately for optional fields, so the 400 you send back actually comes with a message you chose rather than a generic one. For query parameters the same logic applies to <code>request.args<\/code>.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why prefer url_for() over hardcoding URLs in templates and redirects?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">URL Building<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>`url_for()` builds a URL from the view function&#8217;s name, not a string you have to keep in sync by hand. If a route&#8217;s path changes, every `url_for()` call that references it by endpoint name updates automatically; every hardcoded string doesn&#8217;t.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/user\/&lt;username&gt;\u2019)\n\ndef profile(username):\n\n    return f\u201d{username}\u2019s profile\u201d\nurl_for(\u2018profile\u2019, username=\u2019ada\u2019)  # \u2018\/user\/ada\u2019\n<\/code><\/pre><\/div><\/p>\n<p>It also handles escaping of special characters in the arguments, generates absolute paths so relative-path bugs in the browser don&#8217;t bite you, and correctly accounts for a blueprint&#8217;s <code>url_prefix<\/code> or an app mounted somewhere other than the domain root. Hardcoding a path string sidesteps all of that.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you handle uploaded files safely, including the filename?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">File Uploads<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Uploaded files show up on `request.files`, keyed by the form field name, and each one behaves like a file object with a `.save()` method. The part people get wrong is trusting the client-submitted filename directly, since it can be forged to include path traversal characters like `..\/..\/etc\/passwd`.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom werkzeug.utils import secure_filename\n@app.route(\u2018\/upload\u2019, methods=[\u2019POST\u2019])\n\ndef upload_file():\n\n    f = request.files[\u2019the_file\u2019]\n\n    filename = secure_filename(f.filename)\n\n    f.save(f\u201d\/var\/www\/uploads\/{filename}\u201d)\n<\/code><\/pre><\/div><\/p>\n<p><code>secure_filename()<\/code> strips out anything that isn&#8217;t safe for a filesystem path, so it&#8217;s the one line that keeps an upload endpoint from becoming a path traversal vulnerability.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the practical difference between the application context and the request context?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The application context tracks app-level state, `current_app` and `g`, for the duration of a request, CLI command, or other unit of work. The request context tracks the actual incoming HTTP request, `request` and `session`, and only exists while a request is being handled.<\/p>\n<p>Flask automatically pushes a request context when it starts handling a request, and pushing a request context always pushes a matching application context along with it. That&#8217;s why `current_app` and `g` are available anywhere `request` is available, but the reverse doesn&#8217;t hold: you can push an app context on its own, for a CLI command or a script, without a request ever existing.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does RuntimeError: Working outside of application context mean and how do you fix it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>It means something tried to use `current_app`, `g`, or anything built on top of them, outside of any active application context, most often a module-level import that runs before the app exists, or a background thread or script that never pushed one.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef create_app():\n\n    app = Flask(__name__)\n    with app.app_context():\n\n        init_db()  # needs current_app internally, now it has one\n    return app\n<\/code><\/pre><\/div><\/p>\n<p>The fix is to wrap the offending code in `with app.app_context():`, giving it direct access to the app object at that point. If the error shows up inside a real request instead, that usually means the code got pulled out into a background thread or a scheduled job that doesn&#8217;t inherit Flask&#8217;s automatic per-request context.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When would you manually push an app context outside of a request, like in a CLI script?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Any time code needs `current_app`, `g`, or extensions that depend on them, and there&#8217;s no request driving it, a one-off script, a Flask CLI command, a background job, a REPL session for debugging. Flask does push an app context automatically for commands registered with `@app.cli.command()`, but a plain standalone script needs it done by hand.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom myapp import create_app\napp = create_app()\n\nwith app.app_context():\n\n    from myapp.models import User\n\n    User.query.filter_by(active=False).delete()\n<\/code><\/pre><\/div><br \/>\n<\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What automatic escaping does Flask\/Jinja2 apply, and when would you deliberately mark output safe?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jinja2<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Jinja2 autoescapes variables rendered inside templates with `.html`, `.htm`, `.xml`, or `.xhtml` extensions, converting characters like `<` and `>` to their HTML entity equivalents so user-supplied content can&#8217;t inject a script tag or break the page structure. This is on by default and is the main reason Flask templates are safer than building HTML with plain f-strings.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom markupsafe import Markup\nMarkup(\u2018&lt;strong&gt;Hello %s!&lt;\/strong&gt;\u2019) % \u2018&lt;script&gt;bad()&lt;\/script&gt;\u2019\n\n# renders the script tag as visible text, not executable markup\n<\/code><\/pre><\/div><\/p>\n<p>You&#8217;d mark content safe, with the `|safe` filter or by wrapping it in `Markup()`, only when the content genuinely already is trusted HTML, output from a markdown-to-HTML converter you control, for instance. Doing it to silence an escaping error on user input is the exact mistake that reopens the XSS hole autoescaping exists to close.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">If a blueprint and the main app both ship a template with the same relative path, which one wins?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jinja2<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The application&#8217;s own `templates` folder always takes priority over a blueprint&#8217;s `template_folder`. If both define `index.html`, the app&#8217;s copy is the one that actually gets rendered, and the blueprint&#8217;s version is effectively shadowed.<\/p>\n<p>The recommended way to avoid an accidental collision is to namespace a blueprint&#8217;s templates under a subfolder matching the blueprint&#8217;s name, `admin\/templates\/admin\/index.html` rather than `admin\/templates\/index.html`, so it doesn&#8217;t share a relative path with anything the main app or another blueprint might define.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do Flask&#039;s client-side sessions actually work under the hood?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sessions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>By default, `session` data is serialized and stored entirely in a cookie on the client, cryptographically signed using the app&#8217;s `SECRET_KEY` so the browser can&#8217;t tamper with the contents without invalidating the signature. Flask reads that cookie back on the next request and verifies the signature before trusting the data inside it.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\napp.secret_key = b\u2019_5#y2L\u201dF4Q8znxec]\/\u2019\n@app.route(\u2018\/login\u2019, methods=[\u2019POST\u2019])\n\ndef login():\n\n    session[\u2019username\u2019] = request.form[\u2019username\u2019]\n\n    return redirect(url_for(\u2018index\u2019))\n<\/code><\/pre><\/div><\/p>\n<p>The important consequence: a user can still read the contents of that cookie, since signing proves authenticity, not confidentiality. Anything genuinely secret, a password hash, a raw API token, has no business sitting in a Flask session cookie.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why does session usage require a SECRET_KEY, and what&#039;s the blast radius if it leaks?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>The secret key is what signs the session cookie, so Flask can detect if a client modified it. Without one set, calling `session[&#8230;]` raises a `RuntimeError`, since there&#8217;d be nothing to sign with.<\/p>\n<p>If that key leaks, anyone who has it can forge a valid signature for arbitrary session content, session[&#8216;is_admin&#8217;] = True included, and Flask will accept it as legitimate. That&#8217;s the whole reason the docs recommend generating it with `secrets.token_hex()` and keeping it out of version control entirely, in an environment variable or a secrets manager, not hardcoded in `app.py`.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you protect a Flask form endpoint against CSRF?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask itself doesn&#8217;t ship CSRF protection out of the box, that&#8217;s handled by the Flask-WTF extension, which generates a per-session token, embeds it as a hidden field in the form, and rejects any POST that doesn&#8217;t include a matching token.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask_wtf import FlaskForm\n\nfrom flask_wtf.csrf import CSRFProtect\ncsrf = CSRFProtect(app)\nclass LoginForm(FlaskForm):\n\n    username = StringField(\u2018Username\u2019)\n<\/code><\/pre><\/div><\/p>\n<p>The token check works because it relies on something an attacker&#8217;s cross-site request can&#8217;t forge, a value tied to the victim&#8217;s own session, which is exactly the gap plain cookie-based auth without CSRF protection leaves open.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What security risk does the interactive debugger introduce, and how does Flask mitigate it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Werkzeug&#8217;s interactive debugger, shown when an unhandled exception occurs in debug mode, lets you execute arbitrary Python code directly in the browser at the point of the traceback. That&#8217;s exactly as dangerous as it sounds if it&#8217;s ever reachable from the public internet, since anyone who triggers an error can run code on your server.<\/p>\n<p>Flask&#8217;s mitigation is a PIN: the debugger console is locked until you enter a PIN value printed to the server&#8217;s console log at startup, which an outside attacker can&#8217;t see. The real mitigation isn&#8217;t the PIN though, it&#8217;s simply never enabling debug mode in a production deployment in the first place.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does registering a blueprint with url_prefix change its routes?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Blueprints<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Every route defined inside the blueprint gets that prefix prepended automatically, and the blueprint&#8217;s own name becomes a namespace on the endpoint, separated by a dot, so `url_for()` calls need to reference it as `blueprint_name.view_name`.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\napp.register_blueprint(admin_bp, url_prefix=\u2019\/pages\u2019)\n\n# a route defined as \u2018\/dashboard\u2019 inside admin_bp is now served at \u2018\/pages\/dashboard\u2019\n\nurl_for(\u2018admin.dashboard\u2019)  # not just \u2018dashboard\u2019\n<\/code><\/pre><\/div><\/p>\n<p>You can also register the same blueprint more than once, at different prefixes, though not every blueprint is written to support that safely, it depends on whether it assumes it&#8217;s mounted exactly once.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the application factory pattern, and what problem does it solve for testing and config?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Factory<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Instead of creating a single module-level `app = Flask(__name__)` that every other module imports, you wrap app creation in a function, `create_app()`, that builds and returns a fresh app instance each time it&#8217;s called, with extensions initialized and blueprints registered inside.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\ndef create_app(config_name=\u2019production\u2019):\n\n    app = Flask(__name__)\n\n    app.config.from_object(configs[config_name])\n\n    db.init_app(app)\n\n    app.register_blueprint(admin_bp)\n\n    return app\n<\/code><\/pre><\/div><\/p>\n<p>This solves two real problems. Tests can call `create_app(&#8216;testing&#8217;)` to get an isolated app instance with a test database config, without any risk of leftover state from a previous test bleeding into the next one. It also sidesteps circular imports, since extension and blueprint modules no longer need to import a concrete `app` object at module load time, only `current_app` when they&#8217;re actually invoked.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do extensions typically avoid circular imports when used inside an app factory?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Factory<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Most well-behaved Flask extensions split object creation from app binding: you instantiate the extension object at module scope with no app passed in, then call `.init_app(app)` later, inside the factory function, to actually attach it. That lets a models module import the bare extension instance without ever needing to import the concrete `app` object.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n# extensions.py\n\ndb = SQLAlchemy()\n# app.py\n\nfrom extensions import db\ndef create_app():\n\n    app = Flask(__name__)\n\n    db.init_app(app)\n\n    return app\n<\/code><\/pre><\/div><\/p>\n<p>Any module in the project can safely `from extensions import db` without triggering a circular import back to `app.py`, since `db` doesn&#8217;t depend on `app` existing yet at import time.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What problem does Flask-Migrate solve, and what&#039;s it built on top of?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Migrations<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask-Migrate handles schema migrations, generating and applying versioned changes to your database tables as your models evolve, wrapping the Alembic migration library and exposing it through Flask&#8217;s CLI as `flask db init`, `flask db migrate`, and `flask db upgrade`.<\/p>\n<p>Without it, every schema change means writing raw SQL by hand or dropping and recreating tables, which isn&#8217;t viable once there&#8217;s production data you can&#8217;t just wipe. Flask-Migrate&#8217;s `migrate` command diffs your current models against the last known migration state and generates the change script for you to review before applying it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between returning a dict from a view and calling jsonify() yourself?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">JSON<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Returning a plain dict or list from a view is a shortcut Flask added that internally calls `jsonify()` on your behalf, so for the common case they produce an identical response, correct content type, serialized body.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/me\u2019)\n\ndef me():\n\n    return {\u201cusername\u201d: \u201cada\u201d}  # implicitly jsonify()\u2019d\n@app.route(\u2018\/me2\u2019)\n\ndef me2():\n\n    return jsonify({\u201cusername\u201d: \u201cada\u201d})  # explicit, same result\n<\/code><\/pre><\/div><\/p>\n<p>You still need to call `jsonify()` explicitly when you want to combine the JSON body with a custom status code or headers using the tuple return form, since the plain-dict shortcut only covers the simple 200-with-default-headers case.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you validate and serialize incoming JSON without hand-rolling every check?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Validation<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Marshmallow or Pydantic are the two common choices; you define a schema once, and it validates incoming data (raising a clear error listing every invalid field) and serializes outgoing model objects back to JSON-safe dicts, instead of writing `if &#8216;field&#8217; not in data` checks by hand for every endpoint.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom marshmallow import Schema, fields, ValidationError\nclass UserSchema(Schema):\n\n    username = fields.Str(required=True)\n\n    email = fields.Email(required=True)\n@app.route(\u2018\/users\u2019, methods=[\u2019POST\u2019])\n\ndef create_user():\n\n    try:\n\n        data = UserSchema().load(request.get_json())\n\n    except ValidationError as err:\n\n        return jsonify(err.messages), 400\n<\/code><\/pre><\/div><\/p>\n<p>This is also where Flask&#8217;s lack of an opinion becomes visible in interviews: Django REST Framework bundles serializers, Flask leaves the choice to you, so being able to name a real option and explain why you&#8217;d reach for it matters more than it would in a Django-specific interview.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the standard init_app() pattern extensions use, and why does it matter for factories?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Extensions<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>An extension exposing `init_app(app)` separates the extension&#8217;s own object creation from binding it to a specific Flask instance. You create the extension once at module import time with no app, then call `.init_app(app)` inside `create_app()` once the actual app object exists.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nmail = Mail()  # no app yet\ndef create_app():\n\n    app = Flask(__name__)\n\n    mail.init_app(app)  # binds it now\n\n    return app\n<\/code><\/pre><\/div><\/p>\n<p>Without this split, an extension that only supports `Extension(app)` at construction time can&#8217;t be used cleanly with the factory pattern, since the app doesn&#8217;t exist yet when the module defining `mail` gets imported. Most mainstream Flask extensions support both styles for exactly this reason.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">When do you need test_request_context() instead of test_client()?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>test_client()<\/code> is for testing behavior through an actual request\/response round trip, exactly what a real client would see. <code>test_request_context()<\/code> is for when you need a request context active so you can call code that depends on `request`, `session`, or `url_for()` directly, without going through the full view dispatch and response-building machinery.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nwith app.test_request_context(\u2018\/hello\u2019, method=\u2019POST\u2019):\n\n    assert request.path == \u2018\/hello\u2019\n\n    assert request.method == \u2018POST\u2019\n\n    print(url_for(\u2018login\u2019))\n<\/code><\/pre><\/div><\/p>\n<p>You&#8217;d reach for it when unit testing a helper function that uses `url_for()` or reads `request`, in isolation, rather than testing the full view that calls it.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What&#039;s the difference between app.debug and use_reloader, and why might you want one without the other?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Debugging<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Debug mode (`app.debug` or `flask run &#8211;debug`) enables the interactive debugger on unhandled exceptions and more verbose error output. `use_reloader` is the separate setting that restarts the server process automatically whenever it detects a source file change. Debug mode turns both on by default, but they&#8217;re independently controllable.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\napp.run(debug=True, use_reloader=False)\n<\/code><\/pre><\/div><\/p>\n<p>You&#8217;d disable the reloader specifically while running the app under a debugger like `pdb` or an IDE&#8217;s breakpoint tooling, since the reloader spawns a second child process, and stepping through code in the wrong one is a confusing way to lose an afternoon.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you mock an outbound API call inside a view under test?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Patch the function or client that makes the actual network call, `requests.get`, or a wrapped client method, at the point where your view code imports it, using `unittest.mock.patch`, so the test never hits the network and gets a predictable response back instead.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom unittest.mock import patch\n@patch(\u2018myapp.views.requests.get\u2019)\n\ndef test_weather_view(mock_get):\n\n    mock_get.return_value.json.return_value = {\u201ctemp\u201d: 72}\n\n    response = app.test_client().get(\u2018\/weather\u2019)\n\n    assert b\u201972\u2019 in response.data\n<\/code><\/pre><\/div><\/p>\n<p>The detail that trips people up is patching the wrong import path, you patch the name as it&#8217;s looked up in the module under test (`myapp.views.requests.get`), not where `requests` itself is originally defined.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does app.test_client() give you that calling the view function directly doesn&#039;t?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Testing<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Calling a view function directly, `my_view()`, skips Flask&#8217;s entire request dispatch: no URL routing, no request or application context pushed, no before\/after-request hooks, and no conversion of the return value into an actual Response object. If the view depends on `request` or `g`, calling it bare raises the same &#8220;working outside of context&#8221; error you&#8217;d get anywhere else.<\/p>\n<p><code>test_client()<\/code> runs a full simulated request through that entire pipeline, contexts pushed, hooks fired, response object built, so what you&#8217;re testing matches what a real client actually experiences, including status codes, headers, and any middleware in the chain.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why use gunicorn (or another WSGI server) instead of flask run in production, and what does the worker model change?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Deployment<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Gunicorn manages a pool of worker processes (or threads, or async workers depending on the worker class) in front of your Flask app, handling concurrency, worker recycling on crashes, and graceful restarts, none of which the single-process development server does.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">bash<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-bash\">\n\ngunicorn \u2013workers 4 \u2013bind 0.0.0.0:8000 myapp:app\n<\/code><\/pre><\/div><\/p>\n<p>Switching to multiple worker processes is also the point where any in-memory, per-process assumption in your app breaks: a plain Python dict used as a cache, a background counter, an in-memory rate limiter, all become per-worker instead of shared across the whole deployment, silently giving inconsistent results depending on which worker handled a given request.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does Werkzeug&#039;s ProxyFix middleware do and when do you need it?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Deployment<\/span><span class=\"iq-badge iq-badge--medium\">Medium<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>When a Flask app sits behind a reverse proxy like nginx, the proxy terminates the actual client connection, so `request.remote_addr` and the scheme Flask sees default to the proxy&#8217;s own address and connection details, not the real client&#8217;s. `ProxyFix` reads the standard forwarded headers (`X-Forwarded-For`, `X-Forwarded-Proto`, and others) the proxy sets and rewrites the WSGI environ so Flask sees the real client IP and scheme instead.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom werkzeug.middleware.proxy_fix import ProxyFix\napp.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)\n<\/code><\/pre><\/div><\/p>\n<p>Skipping this behind a proxy is a quiet source of bugs: IP-based rate limiting keyed on `remote_addr` ends up rate-limiting the proxy itself instead of individual clients, and `url_for(_external=True)` can generate `http:\/\/` links for a site that&#8217;s actually served over `https:\/\/`.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-dsec iq-dsec--hard\"><div class=\"iq-dsec__row\"><h2 class=\"iq-dsec__h\" id=\"hard\"><span class=\"iq-dsec__dot\" aria-hidden=\"true\"><\/span>Hard questions<\/h2><span class=\"iq-dsec__n\">12<\/span><\/div><div class=\"iq-dsec__bar\" aria-hidden=\"true\"><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How do you attach WSGI middleware to a Flask app, and why wrap wsgi_app instead of app itself?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">WSGI<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>You reassign <code>app.wsgi_app<\/code> to the wrapped version, not <code>app<\/code> itself: <code>app.wsgi_app = ProxyFix(app.wsgi_app)<\/code>. Middleware operates purely at the WSGI callable level, taking an environ and start_response and returning a response, so it doesn&#8217;t need to know anything about Flask&#8217;s routing or Flask-specific attributes.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom werkzeug.middleware.proxy_fix import ProxyFix\napp = Flask(__name__)\n\napp.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)\n<\/code><\/pre><\/div><\/p>\n<p>Wrapping <code>app.wsgi_app<\/code> instead of replacing <code>app<\/code> keeps <code>app<\/code> pointing at your actual Flask instance, so `app.route()`, `app.config`, and every other Flask-level API you rely on elsewhere in the codebase keeps working unchanged. If you replaced <code>app<\/code> itself with the middleware-wrapped object, you&#8217;d lose access to all of that.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What are the rules Flask uses to convert a view function&#039;s return value into a Response?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Responses<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask checks the return value against a fixed set of cases, in order. A Response object of the right type is returned as-is. A plain string becomes a Response with that body, status 200, and content type text\/html. An iterator or generator yielding strings or bytes is treated as a streaming response. A dict or list is passed through `jsonify()` to become a JSON response. A tuple is unpacked as `(body, status)`, `(body, headers)`, or `(body, status, headers)`. Anything else that doesn&#8217;t match is assumed to be a WSGI application itself.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/status\u2019)\n\ndef status():\n\n    return {\u201cok\u201d: True}, 202  # dict body via jsonify, status overridden to 202\n<\/code><\/pre><\/div><\/p>\n<p>The tuple form is the one that trips people up in code review, since it&#8217;s easy to misread `(body, status)` as `(status, body)` in a rushed diff. Knowing the exact order Flask checks these cases in also explains why returning a two-item tuple where the second item is a dict, not an int, behaves as `(body, headers)` instead of erroring.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why is current_app a proxy instead of a direct reference to the Flask instance?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p><code>current_app<\/code> has to resolve to a different actual Flask object depending on which context is active at the moment it&#8217;s accessed, which a plain module-level reference can&#8217;t do. It&#8217;s a `LocalProxy` that, on every attribute access, looks up whatever app is bound to the current context and forwards the call to it.<\/p>\n<p>This matters specifically for the app factory pattern and for extensions. An extension module can&#8217;t import a concrete `app` object at import time, since there might be several apps created by several factory calls in the same process, tests being the obvious case. By deferring resolution to the moment of use, `current_app` lets the same extension code work correctly no matter which app instance is actually handling the current request or command.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does Flask keep request and app globals thread-safe without you managing locks?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Application Context<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Objects like `request`, `session`, `g`, and `current_app` are context-local proxies backed by Python&#8217;s `contextvars` (through Werkzeug&#8217;s local storage). Each thread, or each async task, gets its own isolated slot for these values, so two requests handled concurrently by two threads never see each other&#8217;s `request` object even though the code refers to the exact same name.<\/p>\n<p>Flask pushes the context when it starts handling a request and pops it when the request finishes, binding the current thread or task to that specific request&#8217;s data for the duration. You don&#8217;t manage a lock yourself because the isolation happens at the storage layer, not by serializing access to a shared object.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What does EXPLAIN_TEMPLATE_LOADING do and when would you turn it on?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Jinja2<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Setting the `EXPLAIN_TEMPLATE_LOADING` config value to `True` makes Flask print out, on every single `render_template()` call, exactly which folders it searched and in what order, and whether each one had a matching file. It&#8217;s a debugging aid, not something you&#8217;d leave on in any real environment because of how noisy the output is.<\/p>\n<p>You&#8217;d turn it on specifically when a template is loading the wrong version of itself, usually a blueprint template shadowing issue, and you can&#8217;t otherwise tell which of several template folders Jinja2 actually picked from.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you move session storage server-side instead of the default signed cookie?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Sessions<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask doesn&#8217;t include server-side sessions itself; you&#8217;d add an extension like Flask-Session, which swaps the default cookie-based `SessionInterface` for one backed by Redis, a database, or the filesystem. The cookie the client holds then contains only an opaque session ID, and the actual data lives server-side, looked up by that ID on each request.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask_session import Session\napp.config[\u2019SESSION_TYPE\u2019] = \u2018redis\u2019\n\nSession(app)\n<\/code><\/pre><\/div><\/p>\n<p>The trade-off: server-side sessions can hold larger and more sensitive data safely, since nothing but an opaque ID ever leaves the server, but they add an infrastructure dependency (Redis, in this example) and a bit of latency per request that a signed cookie doesn&#8217;t have.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Why do 404 and 405 errors need special handling when using blueprint-level error handlers?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Blueprints<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>A blueprint&#8217;s `@blueprint.errorhandler(404)` only fires when a 404 is raised explicitly from inside one of that blueprint&#8217;s own view functions, via `abort(404)` or a raised `NotFound`. It does not fire for a genuinely invalid URL that never matched any route in the first place, because a blueprint doesn&#8217;t own a chunk of URL space the way an application does. Flask has no way to know which blueprint&#8217;s handler should apply to a URL that matched nothing.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.errorhandler(404)\n\n@app.errorhandler(405)\n\ndef handle_error(ex):\n\n    if request.path.startswith(\u2018\/api\/\u2019):\n\n        return jsonify(error=str(ex)), ex.code\n\n    return ex\n<\/code><\/pre><\/div><\/p>\n<p>The workaround is to register a single application-level handler and branch on `request.path` yourself, exactly the pattern shown above, rather than expecting per-blueprint 404 handlers to catch unmatched routes.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does nesting blueprints affect endpoint names and URL prefixes?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Blueprints<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Registering one blueprint on another (`parent.register_blueprint(child)`) chains both the name and the URL prefix: the child&#8217;s endpoint name gets the parent&#8217;s name prepended, and the child&#8217;s routes get the parent&#8217;s `url_prefix` prepended too.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nparent = Blueprint(\u2018parent\u2019, __name__, url_prefix=\u2019\/parent\u2019)\n\nchild = Blueprint(\u2018child\u2019, __name__, url_prefix=\u2019\/child\u2019)\n\nparent.register_blueprint(child)\n\napp.register_blueprint(parent)\nurl_for(\u2018parent.child.create\u2019)  # -&gt; \u2018\/parent\/child\/\u2026\u2019\n<\/code><\/pre><\/div><\/p>\n<p>Blueprint-level `before_request` hooks registered on the parent also apply to the child automatically, and if the child doesn&#8217;t have an error handler for a given exception, the parent&#8217;s is tried next. This is genuinely useful for something like an `\/api\/v1` blueprint that nests per-resource child blueprints underneath it, sharing common setup without repeating it in each one.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How would you add rate limiting to a specific API route in Flask?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Rate Limiting<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask-Limiter is the standard choice: you attach it to the app, then apply a `@limiter.limit(&#8230;)` decorator to the specific view, or set an app-wide default and override it per route. It tracks request counts per client, usually keyed by IP address or an authenticated user ID, against a storage backend, in-memory for a single process, Redis for anything running more than one worker.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\nfrom flask_limiter import Limiter\n\nfrom flask_limiter.util import get_remote_address\nlimiter = Limiter(app, key_func=get_remote_address, storage_uri=\u201dredis:\/\/localhost:6379\u2033)\n@app.route(\u2018\/api\/login\u2019, methods=[\u2019POST\u2019])\n\n@limiter.limit(\u201c5 per minute\u201d)\n\ndef login():\n\n    \u2026\n<\/code><\/pre><\/div><\/p>\n<p>The Redis backend detail matters in a real interview: an in-memory limiter resets per process and doesn&#8217;t share state across gunicorn workers, so under multi-worker deployment a &#8220;5 per minute&#8221; limit actually allows 5 times the worker count unless the storage is centralized.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">How does the debugger&#039;s PIN protection actually work, and why isn&#039;t it a substitute for disabling debug mode in production?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Security<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Werkzeug generates a PIN at startup, derived partly from machine-specific values (like the machine ID and a hashed value from `\/etc\/machine-id` or similar) and printed to the server&#8217;s console output, not sent anywhere over the network. Anyone trying to use the debugger console in a browser has to enter that PIN first.<\/p>\n<p>The catch: whoever has shell access to the server console can read the PIN from the logs, and the PIN generation algorithm has had known weaknesses discussed publicly over the years that make it brute-forceable in some configurations. The debugger being reachable from the public internet at all is the actual vulnerability; the PIN is a speed bump on top of a door that shouldn&#8217;t be unlocked in the first place. Debug mode has no place in a production deployment regardless of PIN protection.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">Flask added async view support in 2.0. What&#039;s the catch when an async view calls blocking code?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Async<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Flask 2.0 lets you write `async def` view functions, and Flask runs the coroutine for you under the hood using an async adapter. The catch: Flask itself is still fundamentally a WSGI (synchronous) framework, not ASGI, so an async view doesn&#8217;t get real concurrency for free the way it would in a framework built async-first, like FastAPI.<\/p>\n<p><div class=\"iq-code not-prose\"><div class=\"iq-code__bar\"><span class=\"iq-code__lang\">python<\/span><button class=\"iq-code__copy\" type=\"button\">Copy<\/button><\/div><pre><code class=\"language-python\">\n\n@app.route(\u2018\/data\u2019)\n\nasync def get_data():\n\n    result = await fetch_from_api()  # genuinely async I\/O, fine\n\n    return result\n@app.route(\u2018\/bad\u2019)\n\nasync def bad():\n\n    time.sleep(2)  # blocking call inside an async view still blocks the worker\n\n    return \u2018done\u2019\n<\/code><\/pre><\/div><\/p>\n<p>Calling blocking code, `time.sleep()`, a synchronous database driver, a synchronous HTTP client, inside an `async def` view blocks that worker exactly as it would in a sync view, since there&#8217;s no event loop underneath handling other requests while it waits. The async keyword doesn&#8217;t change the deployment model; you still scale concurrency with more worker processes, not by writing `async def` everywhere.<\/p>\n<p><\/div><\/div><\/div>\n<div class=\"iq-qa\"><button class=\"iq-qa__q\" type=\"button\" aria-expanded=\"false\"><span class=\"iq-qa__qtext\">What breaks when you scale a Flask app across multiple worker processes if you relied on in-memory state like a plain Python dict cache?<\/span><span class=\"iq-qa__meta\"><span class=\"iq-qa__tag\">Scaling<\/span><span class=\"iq-badge iq-badge--hard\">Hard<\/span><span class=\"iq-qa__chev\" aria-hidden=\"true\"><\/span><\/span><\/button><div class=\"iq-qa__a\"><div class=\"iq-qa__a-inner\"><\/p>\n<p>Each gunicorn worker is a separate OS process with its own Python interpreter and its own memory space. A module-level dict used as a cache, a counter, or a simple in-process rate limiter exists independently in every worker, so writes made by a request handled by worker 1 are invisible to a request handled by worker 2.<\/p>\n<p>The fix is moving that shared state somewhere all workers can reach, Redis for a cache or rate limiter, the actual database for anything durable, rather than a language-level data structure that only lives inside one process. This is the single most common &#8220;worked fine locally, broke in production&#8221; bug in Flask apps that were developed and load-tested against a single dev-server process the whole time.<\/p>\n<p><\/div><\/div><\/div>\n<h2>How to prepare for a Flask interview<\/h2>\n<p>Don&#8217;t just reread the quickstart. Build one small thing that forces you past the tutorial-level basics: an app factory with two blueprints registered on it, one Flask-SQLAlchemy model with a real Flask-Migrate migration applied, and one endpoint protected by Flask-Limiter backed by Redis, not the in-memory default. Then deliberately break it, run it under two gunicorn workers and watch an in-memory counter give you inconsistent results, so you&#8217;ve actually seen the failure mode an interviewer is really asking about when they bring up scaling.<\/p>\n<p>Across mock interviews run through LastRoundAI, candidates who can recite what a Blueprint is rarely can explain why a blueprint-level 404 handler doesn&#8217;t fire for an unmatched URL, that gap between reciting a definition and reasoning about the actual dispatch behavior is usually where a Flask interview turns from a pass to a maybe. It&#8217;s not a precise statistic we track, just a pattern that shows up often enough in review to be worth flagging here.<\/p>\n<p>One more thing worth knowing going into an interview in 2026: Flask sitting at 13.2 percent among professional developers while FastAPI climbs to 15.1 percent doesn&#8217;t mean Flask is fading, both numbers come from the same survey and plenty of teams run both, Flask for a stable existing service, FastAPI for a new async-first one. An interviewer asking &#8220;why Flask over FastAPI&#8221; is usually checking whether you can articulate the actual trade-off, WSGI simplicity and a mature extension ecosystem versus native async and automatic OpenAPI docs, rather than looking for a winner.<\/p>\n<h2>Get the reps in before the real thing<\/h2>\n<p>Explaining the difference between application context and request context on paper is not the same as defending it out loud when an interviewer asks what breaks if you skip `app_context()` in a background script. <a href=\"\/products\/mock-interviews\">LastRoundAI&#8217;s mock interview mode<\/a> runs live technical rounds with real-time follow-up questions in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19\/mo if a handful of sessions isn&#8217;t enough runway.<\/p>\n<p>Once your answers hold up under a follow-up, the slower part of the job hunt is usually just getting in front of enough backend and full-stack roles that actually test Flask instead of listing it as a buzzword. <a href=\"\/products\/auto-apply\">Auto-Apply<\/a> queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.<\/p>\n<p>Questions about either product go to contact@lastroundai.com. That&#8217;s the only inbox we check.<\/p>\n<div class=\"iq-sources not-prose\"><div class=\"iq-sources__title\">Sources &amp; further reading<\/div><ul class=\"iq-sources__list\"><li><a href=\"https:\/\/flask.palletsprojects.com\/en\/stable\/quickstart\/\" target=\"_blank\" rel=\"nofollow noopener\">Flask Documentation, Quickstart (3.1.x)<\/a><\/li><li><a href=\"https:\/\/flask.palletsprojects.com\/en\/stable\/appcontext\/\" target=\"_blank\" rel=\"nofollow noopener\">Flask Documentation, The Application Context<\/a><\/li><li><a href=\"https:\/\/flask.palletsprojects.com\/en\/stable\/blueprints\/\" target=\"_blank\" rel=\"nofollow noopener\">Flask Documentation, Modular Applications with Blueprints<\/a><\/li><li><a href=\"https:\/\/survey.stackoverflow.co\/2025\/technology\" target=\"_blank\" rel=\"nofollow noopener\">Stack Overflow Developer Survey 2025, Technology<\/a><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A candidate at a Series B logistics startup once got asked to explain why a database connection opened in one view kept showing up, unclosed, three requests later under load. He&#8217;d used Flask for two years and had never once had to think about it, because his team&#8217;s boilerplate handled it for them. The real&#8230;<\/p>\n","protected":false},"author":4,"featured_media":2039,"comment_status":"open","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"tags":[],"class_list":["post-2014","iq","type-iq","status-publish","has-post-thumbnail","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Flask Interview Questions (2026): 53 Q&amp;A | LastRoundAI<\/title>\n<meta name=\"description\" content=\"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/lastroundai.com\/interview-questions\/flask\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Flask Interview Questions (2026): 53 Q&amp;A | LastRoundAI\" \/>\n<meta property=\"og:description\" content=\"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/lastroundai.com\/interview-questions\/flask\" \/>\n<meta property=\"og:site_name\" content=\"LastRound AI\" \/>\n<meta property=\"og:image\" content=\"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-flask-og.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"33 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask\",\"name\":\"Flask Interview Questions (2026): 53 Q&A | LastRoundAI\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-flask-og.png\",\"datePublished\":\"2026-07-22T05:30:00+00:00\",\"description\":\"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask#primaryimage\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-flask-og.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/iq-flask-og.png\",\"width\":1200,\"height\":630,\"caption\":\"Flask interview questions \u2014 LastRoundAI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\\\/flask#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Interview Questions\",\"item\":\"https:\\\/\\\/lastroundai.com\\\/interview-questions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Flask Interview Questions (2026): Most Asked, With Answers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"name\":\"LastRound AI\",\"description\":\"Interview Assistant prep, tech careers and AI tools\",\"publisher\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#organization\",\"name\":\"LastRound AI\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"contentUrl\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png\",\"width\":400,\"height\":400,\"caption\":\"LastRound AI\"},\"image\":{\"@id\":\"https:\\\/\\\/lastroundai.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Flask Interview Questions (2026): 53 Q&A | LastRoundAI","description":"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/lastroundai.com\/interview-questions\/flask","og_locale":"en_US","og_type":"article","og_title":"Flask Interview Questions (2026): 53 Q&A | LastRoundAI","og_description":"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.","og_url":"https:\/\/lastroundai.com\/interview-questions\/flask","og_site_name":"LastRound AI","og_image":[{"width":1200,"height":630,"url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-flask-og.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"33 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/lastroundai.com\/interview-questions\/flask","url":"https:\/\/lastroundai.com\/interview-questions\/flask","name":"Flask Interview Questions (2026): 53 Q&A | LastRoundAI","isPartOf":{"@id":"https:\/\/lastroundai.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/lastroundai.com\/interview-questions\/flask#primaryimage"},"image":{"@id":"https:\/\/lastroundai.com\/interview-questions\/flask#primaryimage"},"thumbnailUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-flask-og.png","datePublished":"2026-07-22T05:30:00+00:00","description":"53 real Flask interview questions for 2026: app context vs request context, blueprints, WSGI, sessions, Flask-SQLAlchemy, async views, and deployment.","breadcrumb":{"@id":"https:\/\/lastroundai.com\/interview-questions\/flask#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/lastroundai.com\/interview-questions\/flask"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/interview-questions\/flask#primaryimage","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-flask-og.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/07\/iq-flask-og.png","width":1200,"height":630,"caption":"Flask interview questions \u2014 LastRoundAI"},{"@type":"BreadcrumbList","@id":"https:\/\/lastroundai.com\/interview-questions\/flask#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/lastroundai.com\/blog"},{"@type":"ListItem","position":2,"name":"Interview Questions","item":"https:\/\/lastroundai.com\/interview-questions"},{"@type":"ListItem","position":3,"name":"Flask Interview Questions (2026): Most Asked, With Answers"}]},{"@type":"WebSite","@id":"https:\/\/lastroundai.com\/blog\/#website","url":"https:\/\/lastroundai.com\/blog\/","name":"LastRound AI","description":"Interview Assistant prep, tech careers and AI tools","publisher":{"@id":"https:\/\/lastroundai.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/lastroundai.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/lastroundai.com\/blog\/#organization","name":"LastRound AI","url":"https:\/\/lastroundai.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","contentUrl":"https:\/\/lastroundai.com\/blog\/wp-content\/uploads\/2026\/06\/lastroundai-transprant-logo-optimized-BxEo2Wtq.png","width":400,"height":400,"caption":"LastRound AI"},"image":{"@id":"https:\/\/lastroundai.com\/blog\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2014","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq"}],"about":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/types\/iq"}],"author":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/comments?post=2014"}],"version-history":[{"count":1,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2014\/revisions"}],"predecessor-version":[{"id":2065,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/iq\/2014\/revisions\/2065"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media\/2039"}],"wp:attachment":[{"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/media?parent=2014"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lastroundai.com\/blog\/wp-json\/wp\/v2\/tags?post=2014"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}