Intermediate 4 min · March 05, 2026

Django N+1 Queries — ORM Patterns That Kill Performance

One missing select_related caused 500+ queries per page load.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • Django is a high-level Python web framework that follows the Model-View-Template (MVT) architectural pattern
  • Models define database schema and business logic; Views handle HTTP request/response; Templates render HTML dynamically
  • URL dispatcher maps URLs to views using regex or path converters
  • Django ORM abstracts SQL, support for migrations, and lazy evaluation of QuerySets
  • Production trap: N+1 queries from unoptimized ORM usage; use select_related and prefetch_related
  • Biggest mistake: treating templates as presentation-only when they often contain business logic in early iterations

Every time you sign up for a new account on a website, post a comment, or see a personalised dashboard, there's a web framework doing the heavy lifting behind the scenes. Django is the framework that powers Instagram, Pinterest, Disqus, and Mozilla — not because they couldn't build something custom, but because Django handles the boring, dangerous, and repetitive parts of web development so engineers can focus on the actual product. It's not just popular; it's one of the most battle-hardened Python tools in existence.

Before frameworks like Django existed, developers had to manually parse HTTP requests, write raw SQL queries, sanitise every user input by hand, and invent their own way to map URLs to functions. Forget about one bug — you'd be inviting dozens of security vulnerabilities every time you forgot to escape a string. Django solves this by giving you a structured, opinionated foundation: a built-in ORM so you never write raw SQL by accident, an automatic admin panel, CSRF protection enabled by default, and a URL routing system that scales from a single page to thousands of endpoints.

By the end of this article you'll understand the MVT (Model-View-Template) architecture from first principles, know how to set up a real Django project with a database-backed model, wire up URL routes to views, and render dynamic HTML templates — the four pillars that every Django application is built on. You'll also know the classic mistakes that trip up intermediate developers and exactly how to sidestep them.

What is Django Web Framework Basics?

Django's Model-View-Template (MVT) architecture split concerns: models define data and business logic, views handle request/response cycles, and templates present data. Unlike MVC where the controller is separate, Django's view acts as both controller and view. The framework is opinionated — it expects you to follow its conventions. That's not a limitation; it's the reason you can move faster. You'll spend less time deciding how to structure your app and more time building features. The built-in ORM, admin panel, authentication, and security middleware come ready to use, so you don't reach for third-party libraries until you actually need them.

Django Models and the ORM: Mapping Your Data Without SQL

Models are Python classes that inherit from django.db.models.Model. Each attribute represents a database field — CharField, IntegerField, ForeignKey, etc. Django automatically creates a database table for each model. The ORM translates QuerySet operations like filter(), exclude(), annotate() into SQL queries executed lazily. This means you can chain filters without hitting the database until the result is actually needed. Migrations track changes to the model definitions and sync them to the database schema. The admin interface is generated automatically from models, which is a massive time-saver for prototyping and content management. The real power comes from the ORM's ability to generate optimized SQL, but only if you understand its lazy evaluation and eager loading options.

URL Routing and Views: Mapping Requests to Responses

Django's URL dispatcher uses a URLconf — a Python module (usually urls.py) that maps URL patterns to view functions or class-based views. Each pattern is defined with path() or re_path(). Path converters (<int:pk>, <slug:slug>) extract typed parameters from the URL. Views receive an HttpRequest object and return an HttpResponse. Function-based views are simple and explicit; class-based views (CreateView, ListView, DetailView) reduce boilerplate for common CRUD operations. Middleware processes request and response globally — common uses include authentication, CSRF protection, and session management. The order of patterns matters; Django stops at the first match, so place specific patterns before generic ones. Use named URL patterns to avoid hardcoding links.

Django Templates: Dynamic HTML Without Spaghetti

Django's template engine renders HTML files with variable substitution, template tags ({% for %}, {% if %}), and filters ({{ value|date:'Y-m-d' }}). Templates inherit from base templates using {% extends %} and blocks {% block content %}{% endblock %}. This promotes DRY design across pages. The built-in template system is sandboxed — it prevents arbitrary Python execution. For heavy logic, use custom template tags or filters, but avoid putting business logic in templates to preserve separation of concerns. Template fragment caching with the {% cache %} tag reduces rendering time for expensive blocks.

Django Middleware and the Request/Response Cycle

Middleware is a lightweight plugin system that processes requests before they reach the view and responses before they return to the client. Each middleware component is a Python class or function following a specific interface. Common built-in middleware: SecurityMiddleware (HTTPS redirect, HSTS), SessionMiddleware, AuthenticationMiddleware, CSRFMiddleware. Custom middleware can be added for logging, metrics, IP blocking, or modifying headers. The middleware chain is defined in setting MIDDLEWARE (order matters: request goes top-down, response goes bottom-up). Each middleware should be fast and stateless because it runs on every request.

Django Forms and Validation

Django forms handle HTML form rendering, validation, and cleaning. A Form class defines fields with built-in validators (required, max_length, regex). ModelForm automatically generates a form from a model. In the view, you instantiate the form with request.POST or request.FILES, call is_valid(), and access cleaned_data. Validation happens server-side; never trust client-side validation alone. Forms also handle CSRF tokens automatically. Custom validators can be added per field or per form.

Django Admin Customisation

The Django admin interface is generated automatically from your models. You can customise it extensively: list_display shows columns, search_fields enables search, list_filter adds sidebar filters, date_hierarchy adds date drill-down, and actions add bulk operations. Inlines display related models on the same page. The admin is intended for staff users, not end customers. It's great for content management and internal tools. Avoid putting business logic in admin methods; instead, write management commands or separate views.

Django Security Best Practices

Django includes built-in protections for common web vulnerabilities: CSRF tokens on all POST forms, XSS escaping in templates, SQL injection via parameterised ORM queries, clickjacking via X-Frame-Options, and HTTPS redirect via SecurityMiddleware. But these are not automatic if misconfigured. In production, always set DEBUG=False, rotate SECRET_KEY, restrict ALLOWED_HOSTS, use HTTPS with SECURE_SSL_REDIRECT, set CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE. Use django-csp for Content Security Policy headers. Keep Django and dependencies updated to patch known vulnerabilities.

Django vs Other Python Web Frameworks
FeatureDjangoFlaskFastAPI
ArchitectureMVT (full-stack)Minimal, flexibleASGI, async-first
Built-in ORMYes (rich, with migrations)No (SQLAlchemy common)No (SQLAlchemy, Tortoise)
Admin PanelAutomatic from modelsExtensions neededThird-party solutions
REST APIDjango REST Framework (extensive)Flask-RESTful or manualBuilt-in Pydantic validation
Async SupportLimited (via Channels or async views since 3.0)None (WSGI only, until Quattro)Native async from the start
Learning CurveModerate (opinionated, many conventions)Low (minimal boilerplate)Moderate (fast if you know Python types)
Best ForLarge monolithic apps, CMS, rapid prototypingMicroservices, small APIs, prototypesHigh-performance APIs, real-time services

Key Takeaways

  • Django's MVT architecture separates data (models), logic (views), and presentation (templates) but requires discipline to keep them truly separated.
  • The ORM eliminates raw SQL for 90% of queries but introduces N+1 performance traps — always inspect generated queries.
  • URL routing is explicit; pattern order matters and path converters prevent regex debugging nightmares.
  • Templates inherit and compose; keep logic out of templates and use custom filters/tags for presentation logic.
  • Middleware provides global request/response hooks; use it sparingly and ensure each middleware is fast and side-effect-free.
  • Common production pitfalls include missing migrations, lazy query evaluation, and misconfigured CSRF/security settings.

Common Mistakes to Avoid

  • Not using select_related and prefetch_related
    Symptom: Page becomes slow as data grows; database query count skyrockets; server CPU spikes under moderate traffic.
    Fix: Identify loops accessing related objects and add select_related (ForeignKey) or prefetch_related (ManyToMany, reverse) to the queryset in the view.
  • Overusing the admin site for everything
    Symptom: Admin pages become slow, complex, or expose too much data; confusion between admin interface and user-facing UI.
    Fix: Customise the admin site (list_display, search_fields, list_filter), or build separate staff-facing views using Django's form and CRUD capabilities.
  • Putting business logic in templates or views
    Symptom: Hard to test or reuse; templates become cluttered; views grow unreadable.
    Fix: Move business logic to model methods, managers, or service layer classes outside views/templates.
  • Ignoring database indexing
    Symptom: Simple queries become slow as table grows; database CPU increases; `EXPLAIN` shows full table scans.
    Fix: Add indexes to fields used in WHERE, JOIN, ORDER BY, and unique constraints. Use Meta.indexes in models or run RunSQL migrations.
  • Misunderstanding CSRF protection and cookie settings
    Symptom: Users getting CSRF token errors on forms; login cookies not set correctly on HTTPS.
    Fix: Ensure CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE are True in production (HTTPS). Use {% csrf_token %} in every POST form. Do not disable CSRF globally.

Interview Questions on This Topic

  • QExplain the difference between Django's ORM and raw SQL. When would you still write raw SQL in Django?Mid-levelReveal
    The ORM provides an abstraction: Python classes map to tables, QuerySet methods generate SQL. It handles cross-database compatibility and injects parameterised queries to prevent SQL injection. However, raw SQL becomes useful for complex reports, window functions, CTEs, or performance-critical queries where the ORM generates suboptimal SQL. Django provides RawSQL and raw() methods for these cases, but they should be used sparingly and reviewed carefully.
  • QHow does Django's middleware work? Give an example of custom middleware use.JuniorReveal
    Middleware is a processing pipeline: each middleware component receives the request, can modify it, pass it to the next component, and later modify the response. It's implemented as a class with __init__(self, get_response) and __call__(self, request) methods. Example: request timing middleware that measures elapsed time and adds a header. Another example: IP whitelisting middleware that returns 403 for unauthorised IPs.
  • QWhat is the Django request/response cycle? Describe the path a request takes from URL entry to response delivery.SeniorReveal
    1. Web server (Nginx/Apache) receives request and passes to WSGI/ASGI server. 2. Django's WSGI handler creates HttpRequest object. 3. Request passes through middleware chain (process_request hook). 4. URL resolver matches request path to a view in urlpatterns. 5. Middleware process_view hooks run. 6. View function executes (either function-based or class-based). 7. View returns HttpResponse. 8. Response passes through middleware chain (process_response hook). 9. Django returns the response to the WSGI server, which sends it to the web server and client.
  • QExplain the difference between select_related and prefetch_related. When would you use each?SeniorReveal
    select_related performs a SQL JOIN to fetch related objects in a single query. It works for ForeignKey and OneToOneField relations only. prefetch_related performs a separate query per relation and then caches results, supporting many-to-many, many-to-one, and generic relations. Use select_related when you're sure you need the related data and it's a single row relation. Use prefetch_related for collections or when you need to filter on the related set.
  • QHow do you handle database migrations in a production environment with multiple instances?SeniorReveal
    The safest approach is to run migrations during a maintenance window with one instance handling requests while another is taken offline. Use python manage.py migrate with careful ordering: add new fields before removing old ones, run separate migrations for data migrations. For zero-downtime, use a phased approach: deploy code that works with both old and new schema, then run migrations, then deploy cleanup. Tools like django-migration-zero-downtime or stronghold help.

Frequently Asked Questions

What is Django Web Framework in simple terms?

Django is a high-level Python web framework that makes building web apps easier by providing a structure: models for data, views for logic, templates for HTML, and URL routing. It's called 'batteries-included' because it comes with an admin panel, authentication, ORM, and security protections built-in.

What is the difference between Django and Flask?

Django is an opinionated full-stack framework with many built-in features (ORM, admin, auth, forms). Flask is a minimalist microframework that gives you freedom to choose your own components. Django is better for large, monolithic applications with complex business logic; Flask excels in microservices and simple APIs where you want light weight.

How do I debug Django's ORM queries?

You can log all queries using from django.db import connection; print(connection.queries). For more detailed analysis, use django-debug-toolbar in development. In production, set up slow query logging on your database server.

What is the difference between function-based views and class-based views?

Function-based views are explicit, easy to test, and good for custom logic. Class-based views (like ListView, CreateView) reduce boilerplate for common CRUD operations but can be harder to customise because of method resolution order. Many teams prefer function-based views for clarity, class-based for standard patterns.

How do I secure a Django app in production?

Essential settings: DEBUG=False, SECURE_SSL_REDIRECT=True, CSRF_COOKIE_SECURE=True, SESSION_COOKIE_SECURE=True, use HTTPS/SSL, rotate SECRET_KEY, set ALLOWED_HOSTS, install security middleware, and keep Django updated. Also validate user input with forms, use parameterised queries (ORM handles this), and enforce proper authentication/authorisation.

🔥

That's Python Libraries. Mark it forged?

4 min read · try the examples if you haven't

Previous
Flask Web Framework Basics
9 / 51 · Python Libraries
Next
SQLAlchemy Basics