Top Django Interview Questions & Answers

Django interviews test your understanding of its batteries-included architecture — the ORM, admin, and MTV pattern that define how a Django app is structured.

Take Practice Interview Now
Improve your chances of getting selected — rehearse these answers out loud with an AI interviewer and get a scored report in minutes.
Start Free →

1. What is Django's MTV architecture and how does it differ from the traditional MVC pattern?

Django follows a Model-Template-View pattern, which is functionally similar to MVC but with different names for the pieces. The Model handles data structure and business logic via the ORM, the Template handles presentation and is basically Django's version of the View in MVC, and the View is where request-handling logic lives, which is closer to what MVC calls the Controller. So the confusing part for people coming from Rails or classic MVC frameworks is that Django's `View` is really the controller, and the `Template` is the view. Django's dispatch layer connects everything together, so `urls.py` acts as the glue between incoming requests and the right view function or class. It's really just a naming difference more than an architectural one; the separation of concerns is the same.

2. Explain Django QuerySets and why they're lazy.

A QuerySet represents a database query that hasn't necessarily been executed yet; it's an object that describes what you want to fetch, like `User.objects.filter(is_active=True)`, but it doesn't hit the database until you actually need the data. This laziness means you can chain filters, excludes, and ordering calls together, and Django will combine them into a single SQL query rather than running one query per chained call. The query actually executes when you iterate over it, call `len()` on it, or explicitly call something like `list()`, `.exists()`, or `.count()`. This matters a lot in interviews because forgetting this leads to bugs where people assume filtering happens in Python when it's really being deferred to the database, or accidentally trigger multiple queries by re-evaluating a queryset. Django also caches results internally after the first evaluation, so repeated iteration over the same queryset instance doesn't re-hit the database.

3. What are Django migrations and how do makemigrations and migrate differ?

Migrations are Django's way of propagating changes to your models, like adding a field or a new model, into your database schema in a version-controlled way. `makemigrations` inspects your models, compares them against the last known state stored in the migration files, and generates a new migration file describing the schema diff as Python code. `migrate` then actually applies those migration files against the database, running the SQL needed to bring the schema in sync, and it tracks which migrations have already been applied in a table called `django_migrations`. Migrations can be applied, unapplied by migrating to an earlier number, and even edited by hand for data migrations that transform existing rows rather than just altering schema. A common gotcha is that `makemigrations` doesn't touch the database at all, it only writes migration files, so forgetting to run `migrate` afterward is one of the most common beginner mistakes.

4. What is the Django admin and how do you customize it?

The Django admin is an auto-generated interface for managing your application's models, built entirely from introspecting your model definitions. You get a full CRUD interface for free just by registering a model with `admin.site.register(MyModel)`, which is one of Django's most famous batteries-included features. To customize it you typically subclass `ModelAdmin`, where you can control things like `list_display` to choose which fields show in the list view, `search_fields` for the search box, `list_filter` for sidebar filters, and `readonly_fields` or `inlines` for editing related models on the same page. It's genuinely useful in production for internal tooling, though most teams lock it down with proper permissions or additional authentication since it exposes a lot of direct data access. Because it relies on introspection, keeping your model's `__str__` method meaningful also makes the admin's dropdowns and object listings much more usable.

5. What is Django middleware and give an example of when you'd write custom middleware.

Middleware is a chain of hooks that sit between the incoming request and the outgoing response, letting you process or modify requests and responses globally rather than in every single view. Each middleware component is a class with a `__call__` method that receives the next middleware or view in the chain, so you can run code before the view executes, after it returns, or both, and even short-circuit the chain by returning a response early. Django ships with middleware like `SecurityMiddleware`, `SessionMiddleware`, `AuthenticationMiddleware`, and `CsrfViewMiddleware`, and the order in `MIDDLEWARE` in settings matters because it determines the order requests pass through going in and the reverse order going out. A common custom use case is logging request timing, adding a custom header to every response, enforcing a maintenance mode, or attaching a tenant object to the request in a multi-tenant app based on the subdomain. Because middleware runs on every request, it's important to keep it lightweight since a slow middleware becomes a tax on the whole application.

6. What's the difference between function-based views and class-based views?

Function-based views, or FBVs, are just plain Python functions that take a request and return a response, which makes them explicit and easy to read for simple logic, especially with decorators like `@login_required` or `@require_POST`. Class-based views, or CBVs, package view logic into classes and let you use inheritance and mixins to reuse behavior, so Django ships generic CBVs like `ListView`, `DetailView`, `CreateView`, and `UpdateView` that handle common CRUD patterns with very little code. The tradeoff is that CBVs can get harder to trace because the actual logic is spread across several methods like `get_queryset`, `get_context_data`, and `form_valid`, inherited from a chain of parent classes, so newcomers sometimes struggle to figure out what code path actually runs. In practice, most teams use FBVs for one-off or highly custom views and CBVs when there's genuine shared behavior across several views, like several models needing similar list-and-filter pages. Both are ultimately just callables from Django's URL routing perspective, since CBVs expose an `as_view()` classmethod that returns a function-like view.

7. What are the core building blocks of Django REST Framework, specifically serializers and viewsets?

DRF is a toolkit built on top of Django for building APIs, and its two most central concepts are serializers and viewsets. A serializer converts complex data like Django model instances into native Python data types that can then be rendered into JSON, and it also handles the reverse process of validating and deserializing incoming JSON into Python objects, similar in spirit to Django's own Forms but geared toward APIs. `ModelSerializer` is the common shortcut that auto-generates fields and validation logic from a model, much like how `ModelForm` works. A viewset bundles together the logic for a set of related views, typically the standard list, create, retrieve, update, and destroy actions, into a single class, and registering it with a `Router` automatically wires up the appropriate URLs and HTTP methods. Combining `ModelViewSet` with a `ModelSerializer` and a router is the fastest path to a fully working CRUD API in DRF, often in under twenty lines of code.

8. How does Django's settings module work and how do you manage settings across environments like local, staging, and production?

Django's configuration lives in a `settings.py` module, which is just plain Python, so anything from environment variables to conditional logic can drive what values end up there, and Django loads it based on the `DJANGO_SETTINGS_MODULE` environment variable. For managing multiple environments, a common pattern is to split settings into a `base.py` with shared defaults and then `local.py`, `staging.py`, and `production.py` files that import from base and override things like `DEBUG`, `ALLOWED_HOSTS`, database credentials, or which email backend to use. An increasingly common alternative is to keep one settings file and pull environment-specific values from environment variables using something like `django-environ`, which also keeps secrets like `SECRET_KEY` and database passwords out of source control. Whatever the approach, `DEBUG = True` should never ship to production because it leaks stack traces and internal paths to anyone who triggers an error. Settings are also evaluated once at process startup, so changing an environment variable generally requires restarting the application server to take effect.

9. How do Django forms work, and how do they differ from ModelForms?

A Django `Form` defines fields declaratively, similar to how models define fields, and it's responsible for rendering HTML input elements, validating submitted data, and exposing cleaned, type-converted values through `form.cleaned_data` once `is_valid()` has been called. Validation happens in layers, field-level validators run first, then any `clean_<fieldname>` methods you've defined, and finally a form-wide `clean` method for validation involving multiple fields together, like confirming two password fields match. A `ModelForm` is a convenience subclass that generates its fields automatically from a model's fields, and calling `form.save()` will create or update a model instance directly, saving a lot of repetitive boilerplate for basic create and edit forms. The key difference in practice is that plain `Form` is for arbitrary input that may not map to a model, like a contact form or search form, while `ModelForm` is specifically for forms that map closely to one model's fields. Both give you CSRF protection automatically when rendered with `{% csrf_token %}` inside a POST form in a template.

10. Walk through what happens during a Django request/response cycle.

When a request hits a Django app, it first passes through the middleware stack in order, where things like session loading, authentication, and CSRF checks happen before your view even runs. Django then consults the URL resolver, `urls.py`, to match the request path against a URL pattern and figure out which view function or class should handle it, extracting any URL parameters along the way. The view executes, typically querying the database through the ORM, running business logic, and building a response, often by rendering a template with a context dictionary using something like `render()`, which loads the template, resolves template inheritance and tags, and returns an `HttpResponse`. That response then travels back out through the same middleware stack in reverse order, giving each middleware a chance to modify it, like adding security headers, before it's finally sent back to the client. Exceptions raised anywhere in this chain are caught by Django's exception handling, which is what produces the debug error page in development or a custom 500 page in production.

11. What's the difference between select_related and prefetch_related, and how do they help avoid the N+1 query problem?

The N+1 problem happens when you fetch a list of objects with one query and then trigger a separate query for each object's related data as you access it, so listing 50 blog posts and touching `post.author.name` for each one fires 51 queries total instead of one or two. `select_related` solves this for forward foreign key and one-to-one relationships by using a SQL `JOIN` to pull the related object's data into the same query as the main query, so accessing `post.author` afterward doesn't hit the database again. `prefetch_related` is used for reverse foreign keys and many-to-many relationships, where a `JOIN` isn't practical because it would multiply rows, and instead it runs a second separate query to fetch all the related objects at once and matches them up in Python. Choosing the wrong one still works correctly, it just doesn't optimize anything, so `select_related` on a many-to-many field is simply ignored while `prefetch_related` on a forward foreign key still works but is less efficient than `select_related` would have been. Profiling with something like Django Debug Toolbar or `django.db.connection.queries` is the usual way to actually catch N+1 problems rather than guessing.

12. What are Django signals and when would you use them?

Signals let certain senders notify a set of receivers when specific actions happen, decoupling the code that triggers an event from the code that reacts to it. The most commonly used ones are `pre_save` and `post_save`, which fire before and after a model instance is saved, and `pre_delete` and `post_delete` for deletions, along with `m2m_changed` for many-to-many relationship changes. A typical use case is automatically creating a related profile object whenever a new `User` is created, using `post_save` on the `User` model, or invalidating a cache entry whenever a related model changes. Signals are convenient but easy to overuse, since the receiver logic isn't visible at the call site where the save happens, which can make code harder to trace and debug, so many experienced Django developers prefer overriding `save()` directly or using explicit service functions instead. Receivers need to be connected, typically in an app's `ready()` method inside `apps.py`, otherwise they simply won't fire even if they're defined correctly.

13. Explain Django's built-in authentication system and the User model.

Django ships with `django.contrib.auth`, which provides a `User` model along with views, forms, and middleware for handling login, logout, password hashing, and permissions out of the box. Passwords are never stored in plain text, they're run through a hashing algorithm like PBKDF2 by default, and Django handles the salting and hashing automatically whenever you call `set_password()` or use `create_user()`. `AuthenticationMiddleware` attaches a `request.user` object to every request, which is either the logged-in user or an `AnonymousUser` instance if nobody's authenticated, and permission checks like `user.has_perm()` or the `@login_required` decorator build on top of that. For anything beyond the default fields, the recommended approach is to define a custom user model early in a project by subclassing `AbstractUser` or `AbstractBaseUser` and pointing `AUTH_USER_MODEL` at it, because swapping the user model after you already have migrations and foreign keys pointing at `auth.User` is genuinely painful. Django also has a full permissions and groups system built in, so you can assign granular permissions to users directly or through groups without writing custom authorization logic.

14. How does Django protect against CSRF attacks?

Cross-Site Request Forgery is when a malicious site tricks a logged-in user's browser into submitting a request to your app that performs some unwanted action, relying on the browser automatically sending the user's session cookie along with it. Django defends against this with `CsrfViewMiddleware`, which requires state-changing requests like POST to include a secret token tied to the user's session, and that token isn't something an attacker on a different origin could know or guess. In templates, you include this token by putting `{% csrf_token %}` inside your `<form>` tag, which Django renders as a hidden input, and for APIs or AJAX calls you typically send it back in a custom header like `X-CSRFToken` that JavaScript reads from a cookie. If the token is missing or doesn't match, Django rejects the request with a 403 rather than processing it. It's worth noting CSRF protection is specifically about cookie-based session authentication, so token-based DRF APIs using something like JWT in an Authorization header don't rely on CSRF protection the same way, since there's no ambient cookie being automatically attached by the browser.

15. What's the philosophical difference between Django and Flask?

Django describes itself as a batteries-included framework, meaning it ships with an ORM, an admin interface, authentication, forms, a templating engine, and a set of conventions for how a project should be structured, all designed to work together out of the box. Flask is a microframework by design, giving you routing and a minimal core, and leaving decisions like which ORM to use, how to handle forms, or how to structure the project entirely up to you, which usually means assembling your own stack from separate libraries like SQLAlchemy and Flask-WTF. That tradeoff shows up directly in productivity versus flexibility, Django gets you to a working CRUD app faster because so much is decided for you, while Flask gives more control over architecture at the cost of more upfront decisions and boilerplate. Django's conventions also mean that most Django codebases look structurally similar to each other, which helps onboarding, while Flask projects can vary wildly depending on the libraries a given team chose to bring in. Neither is strictly better, it really comes down to whether you want a prescriptive full-stack framework for a fairly standard web app or a lightweight, unopinionated core for something more custom or for a small API service.

16. How does Django's URL routing work?

Django resolves incoming requests to views through URLconf modules, typically starting at the `ROOT_URLCONF` setting which points to a root `urls.py`, and each app usually defines its own `urls.py` that gets included into that root file with `include()`. URL patterns are defined using `path()`, which supports converters like `<int:pk>` or `<slug:slug>` to capture and type-convert parts of the URL, or `re_path()` for cases that need full regular expression matching. Django tries patterns in order and uses the first one that matches, so pattern ordering matters, particularly when a more general pattern could shadow a more specific one further down the list. Named URL patterns, using the `name` argument, let you reference a URL from templates with `{% url %}` or from Python code with `reverse()`, so you can change the actual URL string in one place without hunting down every hardcoded link across the codebase. Namespacing with `app_name` is also common in larger projects so that multiple apps can each define a view called something like `detail` without their URL names colliding.

17. What's the difference between ForeignKey, ManyToManyField, and OneToOneField in Django models?

A `ForeignKey` represents a many-to-one relationship, where many rows in one table can point to a single row in another, like many `Order` objects belonging to one `Customer`, and Django adds a column storing the related object's primary key along with a reverse accessor like `customer.order_set` unless you set a `related_name`. A `ManyToManyField` represents relationships where either side can be linked to multiple rows on the other side, like `Article` and `Tag`, and Django implements it under the hood using a hidden join table, or an explicit one if you pass a `through` model when you need extra fields on the relationship itself, like a timestamp for when the tag was applied. A `OneToOneField` is essentially a `ForeignKey` with a uniqueness constraint added, guaranteeing at most one related row on each side, and it's commonly used for extending the built-in `User` model with a `Profile` model without touching `auth.User` directly. Choosing between these usually comes down to cardinality, and getting it wrong is a common design mistake, like using a `ForeignKey` where a `ManyToManyField` was actually needed, which then requires a schema migration to fix later. On the database level, `ForeignKey` and `OneToOneField` both correspond to a foreign key column with an index, while `ManyToManyField` doesn't create any column on either model at all.

Take Practice Interview Now
Improve your chances of getting selected — rehearse these answers out loud with an AI interviewer and get a scored report in minutes.
Start Free →
Related topics
Node.jsSpring BootExpress.jsASP.NET Core