Skip to content
Home Interview Django Interview Questions Answered — ORM, Views, Middleware & More

Django Interview Questions Answered — ORM, Views, Middleware & More

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Python Interview → Topic 4 of 4
Django interview questions explained with real-world context, runnable code, and the WHY behind every answer.
⚙️ Intermediate — basic Interview knowledge assumed
In this tutorial, you'll learn
Django interview questions explained with real-world context, runnable code, and the WHY behind every answer.
  • Django's MVT puts the 'Controller' role in the URL dispatcher — Views are coordinators, not controllers. Business logic belongs in fat models or service layers, never in templates.
  • QuerySets are lazy — the database is only hit when you iterate, slice, or call list(). Chain filters freely; use select_related() for FK joins and prefetch_related() for M2M/reverse FK to kill N+1 queries.
  • Middleware executes top-to-bottom on requests and bottom-to-top on responses. Order in MIDDLEWARE matters — SessionMiddleware must always precede AuthenticationMiddleware or you'll get an AttributeError.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

Think of Django like a fully-equipped restaurant kitchen. The menu is your URL routing, the chefs are your views, the pantry is your database accessed through the ORM, and the health inspector rules are your middleware. An interviewer isn't just checking if you know the kitchen exists — they want to know if you can run a dinner service under pressure. These questions reveal whether you understand how the whole kitchen works together, not just how to boil water.

Django powers some of the most-visited sites on the planet — Instagram started on it, Pinterest scaled with it, and thousands of startups ship with it every year. When a company posts a Django backend role, they're not looking for someone who memorised the docs. They want engineers who understand the framework's design decisions deeply enough to bend them when business requirements get weird. That's what separates a candidate who gets an offer from one who gets a polite rejection email.

The problem with most Django interview prep is that it's surface-level. Lists of questions with one-line answers that don't explain why Django works the way it does. That leaves you vulnerable the moment an interviewer asks a follow-up — 'okay, but why would you choose that approach?' — and you freeze. Real interviews dig into trade-offs, failure modes, and architectural judgment, not just API recall.

By the end of this article you'll be able to explain Django's MVT architecture, the ORM query lifecycle, middleware execution order, signals vs direct calls, and caching strategies — each with the real-world context that makes your answers land. You'll also know the three mistakes that silently kill otherwise good Django interviews.

Django's MVT Architecture — Why It's Not Quite MVC

Almost every Django interview starts here, and almost every candidate fumbles it by saying 'Django uses MVC.' It doesn't — not exactly. Django uses MVT: Model, View, Template. The naming shift isn't cosmetic; it reflects a genuine architectural difference worth understanding.

In classic MVC, the Controller handles HTTP requests, decides what data to fetch, and tells the View what to render. In Django, that controller logic lives in the View function or class. Django's Template is purely the presentation layer — it has no business logic, and the framework enforces that through the deliberately limited template language. The 'Controller' in the traditional sense is Django's URL dispatcher itself, which routes the incoming request to the right view.

Why does this matter in an interview? Because understanding MVT shows you understand Django's philosophy: keep business logic in Python (views and models), keep presentation in templates, and trust the framework to wire them together. When you understand that philosophy, you make better decisions — like knowing that fat models and thin views is a Django best practice, not just a style preference.

Interviewers love asking 'where would you put business logic in a Django app?' The wrong answer is 'in the template.' The right answer is 'in the model or a service layer, with the view acting as a thin coordinator.'

io.thecodeforge.mvt_example.py · PYTHON
123456789101112131415161718192021222324252627
package io.thecodeforge;

# ─── models.py ───────────────────────────────────────────────────────────────
# The Model owns the data AND the business rules around that data.
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    base_price = models.DecimalField(max_digits=8, decimal_places=2)
    is_on_sale = models.BooleanField(default=False)

    def discounted_price(self):
        """Business logic belongs in the model — fat models, thin views."""
        if self.is_on_sale:
            return self.base_price * 0.80  # 20% discount
        return self.base_price

# ─── views.py ────────────────────────────────────────────────────────────────
from django.shortcuts import render
from .models import Product

def product_list(request):
    on_sale_products = Product.objects.filter(is_on_sale=True)
    context = {
        'products': on_sale_products,
    }
    return render(request, 'shop/product_list.html', context)
▶ Output
# Django evaluates QuerySet in the template layer or upon explicit evaluation.
# SQL: SELECT * FROM shop_product WHERE is_on_sale=True
💡Interview Gold:
When asked 'what's the difference between MVC and MVT?', say: 'In Django's MVT, the URL dispatcher plays the role of the Controller, routing requests to Views. Django's Views are closer to MVC Controllers — they coordinate data fetching and template rendering. The Template is strictly presentational, which is enforced by the template language's intentional limitations.' That answer shows architectural understanding, not just terminology recall.

Django ORM Deep Dive — QuerySets, Lazy Evaluation & the N+1 Problem

The ORM is where most Django interviews separate candidates. Everyone knows .filter() and .all(). The real question is whether you understand when the database is actually hit — because getting that wrong kills performance in production.

Django QuerySets are lazy. When you write Product.objects.filter(is_on_sale=True), nothing touches the database. Django builds a description of the query in memory. The database is only hit when you iterate, slice, call list(), or access len() on the QuerySet. This design lets you chain filters efficiently without redundant round-trips.

The N+1 problem is the most common ORM performance trap — and interviewers know it. It happens when you load a list of objects and then access a related object on each one inside a loop. That's one query to get the list, then N more queries for each related record. On a list of 500 orders, you've just fired 501 database queries instead of 2.

The fix is select_related() for ForeignKey/OneToOne relationships (SQL JOIN) and prefetch_related() for ManyToMany or reverse ForeignKey relationships (separate optimised query). Knowing which one to use — and why they work differently under the hood — is what makes you stand out.

io.thecodeforge.orm_patterns.py · PYTHON
12345678910111213141516171819202122
package io.thecodeforge;

from .models import Book, Author

# ✅ THE FIX — select_related() for ForeignKey (SQL JOIN, 1 query total)
def book_list_fast(request):
    # Django generates: SELECT book.*, author.* FROM book
    #                   INNER JOIN author ON book.author_id = author.id
    books = Book.objects.select_related('author').all()

    for book in books:
        # author data is already in memory — ZERO extra queries here
        print(book.author.name)

    return render(request, 'books/list.html', {'books': books})

# ✅ prefetch_related() for reverse FK / ManyToMany (2 queries, not N+1)
def author_list_with_books(request):
    # Query 1: SELECT * FROM author
    # Query 2: SELECT * FROM book WHERE author_id IN (1, 2, 3, ...)
    authors = Author.objects.prefetch_related('books').all()
    return render(request, 'books/authors.html', {'authors': authors})
▶ Output
# SELECT_RELATED results in a single SQL JOIN.
# PREFETCH_RELATED results in two separate SELECT queries with an 'IN' clause.
⚠ Watch Out:
Using select_related() on a ManyToMany field won't work — it silently falls back to N+1 behaviour without raising an error. Always use prefetch_related() for M2M and reverse ForeignKey. A quick way to catch N+1 in development: install django-debug-toolbar and watch the SQL panel — it highlights duplicate queries in red.

Middleware — Django's Request/Response Pipeline Explained

Middleware is one of those Django concepts that interviewers love because it reveals whether you understand the framework's internals or just its surface API. Most candidates know middleware exists for authentication and CSRF. Fewer can explain the execution order, and almost none can write custom middleware correctly on the spot.

Think of middleware as a stack of airport security layers. Your request passes through each layer on the way in, reaches the view (the gate), and then passes back through each layer in reverse on the way out. Django's MIDDLEWARE setting in settings.py defines this stack — top to bottom for requests, bottom to top for responses.

This bidirectional flow matters. SecurityMiddleware sits at the top intentionally — it enforces HTTPS redirects before any other processing happens. SessionMiddleware must come before AuthenticationMiddleware because auth needs the session to be set up first. Swap them and you get a cryptic AttributeError at runtime.

io.thecodeforge.middleware.py · PYTHON
12345678910111213141516171819202122
package io.thecodeforge;

import time
from django.http import JsonResponse

class RequestTimingMiddleware:
    """
    TheCodeForge Senior Pattern: Uses monotonic clock for reliability.
    """
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # ── INBOUND ──
        start_time = time.monotonic()

        response = self.get_response(request)

        # ── OUTBOUND ──
        duration = (time.monotonic() - start_time) * 1000
        response['X-Response-Time-MS'] = f"{duration:.2f}"
        return response
▶ Output
X-Response-Time-MS: 12.45
🔥Pro Tip:
The __init__ method receives get_response — a callable representing the rest of the middleware stack plus the view. The middleware doesn't know or care what's below it. This is the Chain of Responsibility design pattern in action. Mentioning design patterns in a Django interview immediately signals senior-level thinking.

Django Signals vs Direct Calls — When Decoupling Costs You

Signals are one of Django's most misunderstood features, and they're a favourite interview topic because they expose how a candidate thinks about system design trade-offs — not just Django syntax.

Django signals implement the Observer pattern. When something happens — a model is saved, a user logs in, a request finishes — Django broadcasts a signal. Any code that has 'subscribed' to that signal runs automatically. The appeal is decoupling: the model that fires post_save doesn't need to know about the email sender, the audit logger, or the cache invalidator. They all subscribe independently.

But here's where interviewers trip people up: signals are not free. They make code flow implicit and hard to trace. When a User is saved and five signal handlers fire across three different apps, a new team member reading the save code has no idea any of that happens. That's a maintenance burden.

io.thecodeforge.signals.py · PYTHON
1234567891011121314
package io.thecodeforge;

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile

@receiver(post_save, sender=User)
def manage_user_profile(sender, instance, created, **kwargs):
    """
    Senior Approach: Use 'created' flag to avoid unnecessary DB writes on updates.
    """
    if created:
        UserProfile.objects.create(user=instance)
▶ Output
✅ Profile created for user: forge_admin
⚠ Watch Out:
If you forget the ready() method in apps.py to import your signals, your @receiver decorators never register — and the signal fires with no handlers attached. No error is raised. The code just silently doesn't work. This is one of the most common Django bugs in production and a favourite 'gotcha' interview question.
Aspectselect_related()prefetch_related()
Relationship typeForeignKey, OneToOneFieldManyToManyField, reverse ForeignKey
SQL strategySingle JOIN queryTwo separate queries + Python join
Number of queriesAlways 1Always 2 (regardless of row count)
Best forAccessing a parent object from a childAccessing many children from a parent
Memory usageLower — one result setHigher — two result sets merged in Python
Can be chained with filter()Yes — filters apply to JOINYes — with Prefetch() object for fine control
Works across database shardsNo — JOIN requires same DBYes — queries can hit different DBs

🎯 Key Takeaways

  • Django's MVT puts the 'Controller' role in the URL dispatcher — Views are coordinators, not controllers. Business logic belongs in fat models or service layers, never in templates.
  • QuerySets are lazy — the database is only hit when you iterate, slice, or call list(). Chain filters freely; use select_related() for FK joins and prefetch_related() for M2M/reverse FK to kill N+1 queries.
  • Middleware executes top-to-bottom on requests and bottom-to-top on responses. Order in MIDDLEWARE matters — SessionMiddleware must always precede AuthenticationMiddleware or you'll get an AttributeError.
  • Use signals for genuine cross-app decoupling (like hooking into Django's built-in User model). For same-app logic, a direct function call is clearer, easier to test, and easier for future developers to trace.

⚠ Common Mistakes to Avoid

    Calling QuerySet methods inside a template tag loop
    Symptom

    Django Debug Toolbar shows hundreds of identical SQL queries for a single page load —

    Fix

    Move all ORM calls into the view, use select_related()/prefetch_related(), and pass fully evaluated data to the template. Templates should never trigger database queries.

    Forgetting to import signals in apps.py ready()
    Symptom

    Signal handlers silently never fire — no error, no traceback, just missing behaviour in production —

    Fix

    Add import yourapp.signals inside the ready() method of your AppConfig class. The import itself registers the @receiver decorators as a side effect.

    Putting business logic in templates using custom template tags
    Symptom

    Price calculations, discount logic, or access control checks scattered across .html files, impossible to unit test —

    Fix

    Move all logic to model methods or service functions, call them in the view, and pass simple values to the template. Ask yourself: 'Could I test this without a browser?' If no, it's in the wrong place.

Interview Questions on This Topic

  • QLeetCode Standard: Given a Django Model with an N+1 issue, how do you optimize it using select_related vs prefetch_related?
  • QArchitectural: Explain the Django request-response lifecycle from Gunicorn to the Template engine.
  • QDatabase: How would you implement database sharding or read-replicas in a Django project using 'DATABASE_ROUTERS'?
  • QSecurity: What is the difference between AuthenticationMiddleware and RemoteUserMiddleware, and how does Django protect against Session Fixation?

Frequently Asked Questions

What is the N+1 problem in Django and how do you fix it?

The N+1 problem occurs when a query fetches 'N' records, and for each record, an additional query is fired to fetch related data. You fix this using select_related() (for ForeignKey/OneToOne) to perform a SQL JOIN, or prefetch_related() (for ManyToMany) to fetch all related data in one extra bulk query.

How do you handle heavy, long-running tasks in a Django view?

Long-running tasks like sending emails or processing images should never be done in the request-response cycle. Instead, use an asynchronous task queue like Celery with a broker like Redis or RabbitMQ. The view should trigger the task and return an immediate response.

What are Django's generic class-based views (GCBVs) and when should you use them?

GCBVs are pre-built views for common patterns like ListView, DetailView, and CreateView. They follow the DRY (Don't Repeat Yourself) principle. Use them for standard CRUD operations to speed up development, but stick to function-based views for complex, bespoke logic.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousPython Data Structures Interview Q
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged