Master these 31 carefully curated interview questions to ace your next Python django interview.
Django is a Python web framework following the MVT (Model-View-Template) pattern for rapid, secure web development.
Django's MVT: Model (database layer, ORM), View (business logic, handles requests), Template (presentation layer, HTML). Unlike MVC, Django's View is the controller. Key features: built-in admin, ORM, authentication, URL routing, template engine, middleware, and security features (CSRF, XSS, SQL injection protection). 'Batteries included' philosophy means most web features are built-in.
Django's ORM maps Python classes to database tables, allowing you to query databases using Python code instead of SQL.
Models define schema: class User(models.Model). Field types: CharField, IntegerField, ForeignKey, ManyToManyField. QuerySet API: User.objects.filter(age__gte=18).order_by('name'). Supports: chaining, aggregation, annotation, F expressions (database-level operations), Q objects (complex queries). Migrations auto-generate from model changes. Supports PostgreSQL, MySQL, SQLite, Oracle. Raw SQL available when needed: User.objects.raw('SELECT ...').
Migrations are auto-generated Python files that track and apply database schema changes based on model definitions.
Workflow: (1) Modify models.py. (2) python manage.py makemigrations — generates migration file. (3) python manage.py migrate — applies to database. Migrations are version-controlled. Support: creating/dropping tables, adding/removing columns, data migrations (RunPython). Squash migrations to consolidate. Commands: showmigrations (list), sqlmigrate (show SQL), migrate --fake (mark without running). Always test migrations in staging before production.
FBVs are simple functions handling requests; CBVs use classes with built-in methods for common CRUD patterns.
FBVs: def my_view(request): return HttpResponse('Hello'). Simple, explicit, good for unique logic. CBVs: class MyView(View): def get(self, request): ... — provide mixins (LoginRequiredMixin, CreateView, ListView) for DRY code. Generic CBVs handle CRUD automatically. FBVs are easier to understand; CBVs reduce boilerplate for standard operations. Choose FBVs for custom logic, CBVs for standard CRUD. DRF uses CBVs extensively (APIView, ViewSet).
Django uses urlpatterns in urls.py to map URL patterns to view functions/classes using path() or re_path().
urls.py: urlpatterns = [path('articles/<int:id>/', views.article_detail)]. Path converters: <int:>, <str:>, <slug:>, <uuid:>. Include app URLs: path('api/', include('myapp.urls')). Named URLs: path('home/', views.home, name='home'). Reverse URL: reverse('home'). Namespace: app_name = 'blog'. URL resolution is top-down — first match wins. Django 2.0+ uses path() (simplified), re_path() for regex patterns.
Django provides built-in protection against CSRF, XSS, SQL injection, clickjacking, and includes secure authentication.
Security features: (1) CSRF: middleware adds token to forms, validates on POST. (2) XSS: template auto-escaping ({{ variable }} is escaped). (3) SQL Injection: ORM parameterizes queries. (4) Clickjacking: X-Frame-Options middleware. (5) Password storage: PBKDF2 hashing with salt. (6) HTTPS: SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS. (7) Content-type sniffing protection. (8) Session security: SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE. Always run python manage.py check --deploy before deploying.
DRF is a toolkit for building Web APIs in Django with serializers, viewsets, authentication, and browsable API.
Key components: (1) Serializers: convert models to JSON and validate input (ModelSerializer). (2) ViewSets: combine list/create/retrieve/update/delete in one class. (3) Routers: auto-generate URL patterns. (4) Authentication: Token, Session, JWT (via simplejwt). (5) Permissions: IsAuthenticated, IsAdminUser, custom. (6) Pagination: PageNumber, LimitOffset, Cursor. (7) Filtering: django-filter integration. (8) Throttling: rate limiting. (9) Browsable API for easy testing. Most popular choice for Django APIs.
Middleware is a framework of hooks that process requests/responses globally, running before views and after responses.
Middleware chain: Request → SecurityMiddleware → SessionMiddleware → AuthenticationMiddleware → View → Response (reverse order). Custom middleware: class MyMiddleware: def __init__(self, get_response): ... def __call__(self, request): ... response = self.get_response(request) ... return response. Built-in: CSRF, session, auth, GZip, locale. Use for: logging, request timing, modifying headers, IP blocking, custom auth. Order in MIDDLEWARE setting matters — security first.
Use select_related, prefetch_related, values/only, indexing, and avoid N+1 queries.
Techniques: (1) select_related(): JOIN for FK/OneToOne (single query). (2) prefetch_related(): separate query for M2M/reverse FK. (3) .only()/.defer(): load specific fields. (4) .values()/.values_list(): return dicts/tuples instead of objects. (5) Database indexes on filtered/ordered fields. (6) django-debug-toolbar to see queries. (7) Bulk operations: bulk_create, bulk_update. (8) Avoid queryset evaluation in loops. (9) Use .count()/.exists() instead of len()/bool(). (10) Raw SQL for complex queries.
Django supports per-site, per-view, and template fragment caching with backends like Redis, Memcached, and database.
Backends: Redis (django-redis), Memcached, database, file-based, local memory. Levels: (1) Per-site: CacheMiddleware caches entire site. (2) Per-view: @cache_page(60*15) decorator. (3) Template fragment: {% cache 500 sidebar %}. (4) Low-level: cache.set('key', value, timeout). Cache key versioning, cache invalidation strategies. Use Vary header for user-specific caching. Django's cache framework is configurable — switch backends without code changes.
Django 3.1+ supports async views with async def, ASGI servers (Daphne/Uvicorn), and async ORM operations in Django 4.1+.
async def my_view(request): data = await async_operation(). Deploy with ASGI server: uvicorn project.asgi:application. Django 4.1 added async ORM: await Model.objects.aget(). Async support: views, middleware, tests. Still sync: some ORM operations, template rendering. Use sync_to_async() wrapper for sync-only code. Channels library for WebSockets. Benefits: handle more concurrent connections for I/O-heavy endpoints. Mix sync and async views in same project.
Django uses transaction.atomic() for database transactions, ensuring all operations succeed or all are rolled back.
Usage: with transaction.atomic(): ... or @transaction.atomic decorator. Django auto-commits each query by default. ATOMIC_REQUESTS setting wraps each view in a transaction. Savepoints: nested atomic() blocks create savepoints. transaction.on_commit() runs code after successful commit (send emails, clear cache). select_for_update() locks rows for concurrent access. DatabaseError/IntegrityError trigger automatic rollback. Always handle TransactionManagementError for operations outside atomic blocks.
Signals allow decoupled components to notify each other of events like model save, delete, or request start/finish.
Built-in: pre_save, post_save, pre_delete, post_delete, m2m_changed, request_started, request_finished. Usage: @receiver(post_save, sender=User) def create_profile(sender, instance, created, **kwargs). Register in apps.py ready() method. Use for: creating related objects, sending notifications, updating caches, logging. Avoid for: complex business logic (hard to debug), cascading signals. Alternatives: override model.save(), use service layer functions. Signals add implicit coupling — use judiciously.
Use django-debug-toolbar to find N+1 queries, add caching (Redis), optimize ORM queries, and add database indexes.
Steps: (1) Install django-debug-toolbar — check SQL queries per page (target < 10). (2) Fix N+1 queries with select_related/prefetch_related. (3) Add database indexes on frequently filtered/ordered columns. (4) Implement Redis caching for expensive queries. (5) Use pagination for large querysets. (6) Profile with django-silk or cProfile. (7) Consider read replicas for heavy read loads. (8) CDN for static/media files. (9) Async views for I/O-bound endpoints. (10) Database connection pooling (django-db-connection-pool).
Use Gunicorn/Uvicorn as WSGI/ASGI server, Nginx as reverse proxy, PostgreSQL, Redis for caching, and Docker for containerization.
Production checklist: (1) DEBUG=False, ALLOWED_HOSTS set. (2) Gunicorn/Uvicorn workers (2*CPU+1). (3) Nginx reverse proxy: serve static files, SSL termination, load balancing. (4) PostgreSQL with connection pooling (pgBouncer). (5) Redis for cache and sessions. (6) Celery for background tasks. (7) Static files: python manage.py collectstatic + whitenoise or CDN. (8) Environment variables for secrets. (9) CI/CD pipeline with tests. (10) Monitoring: Sentry for errors, Prometheus/Grafana for metrics. (11) docker-compose for orchestration.
Instagram runs the largest Django deployment, using extensive caching, sharding, and custom optimizations to handle billions of users.
Instagram's architecture: (1) Django serves the web tier. (2) PostgreSQL with horizontal sharding. (3) Memcached/Redis for aggressive caching. (4) Cassandra for feed storage. (5) RabbitMQ for async task processing (Celery). (6) Custom Django middleware and optimizations. (7) They contribute back to Django (e.g., async support). Key lesson: Django scales if you optimize queries, cache aggressively, and offload heavy work. They handle millions of requests per second with Python/Django.
Use Django REST Framework for the API layer, with a machine learning model served via a separate service and cached in Redis.
Architecture: (1) DRF endpoint: /api/recommendations/{user_id}/. (2) ML model trained offline (collaborative filtering, content-based). (3) Model predictions cached in Redis with TTL. (4) Fallback to popular items if cache miss. (5) A/B testing different algorithms. (6) Track user interactions (clicks, skips) for model training. (7) Celery periodic tasks retrain models. (8) Serializer formats response with track metadata. (9) Pagination for recommendation lists. (10) Rate limiting per user.
Use a counter-based ID encoded to Base62 for short URLs, with Redis caching and PostgreSQL for persistence.
Implementation: (1) Model: ShortURL(original_url, short_code, created_at, click_count). (2) Generate short_code: counter → base62 encode (a-z, A-Z, 0-9). (3) POST /api/shorten — create entry, return short URL. (4) GET /{code} — lookup and redirect (301/302). (5) Redis cache for hot URLs. (6) Analytics: track clicks, referrers, geography. (7) Custom domain support. (8) URL validation and sanitization. (9) Rate limiting on creation. (10) Expiration policy for old URLs. Scale: partition by code prefix.
select_related uses SQL JOIN for ForeignKey/OneToOne; prefetch_related uses separate queries for ManyToMany/reverse FK.
select_related: single query with JOIN — ideal for ForeignKey and OneToOneField. Reduces N+1 queries. prefetch_related: executes separate query per relation, joins in Python — ideal for ManyToManyField and reverse ForeignKey. select_related('author') generates LEFT JOIN. prefetch_related('tags') runs 2 queries: one for objects, one for all related tags. Use Prefetch object for complex filtering. Django Debug Toolbar helps identify N+1 query problems. Always profile with assertNumQueries in tests.
Decorators are functions that modify or extend other functions without changing their source code, using the @decorator syntax.
A decorator takes a function, wraps it with additional logic, and returns the wrapper. @decorator is syntactic sugar for func = decorator(func). Common patterns: @login_required (access control), @cache (memoization), @staticmethod, @property. Decorators with arguments: need a decorator factory (function returning a decorator). Use functools.wraps to preserve original function metadata. Class-based decorators use __call__. Django examples: @csrf_exempt, @require_http_methods, @transaction.atomic.
Generators use yield to produce values lazily one at a time, saving memory compared to building entire lists.
Functions with yield become generators — they pause execution and resume on next(). Memory efficient: process one item at a time instead of loading all into memory. Generator expressions: (x*2 for x in range(1000000)). Use cases: large file processing, infinite sequences, data pipelines, streaming responses in Django. send() sends values into generators. yield from delegates to sub-generators. asyncio uses generators internally. StopIteration signals completion.
Django wraps each request in a transaction by default, with atomic() decorator for explicit transaction control.
ATOMIC_REQUESTS=True wraps entire view in transaction — rolls back on exception. transaction.atomic() creates savepoints for nested blocks. on_commit() runs callbacks after successful commit (send emails, trigger Celery tasks). select_for_update() locks rows for UPDATE. Isolation levels configurable per database. Common pitfall: code inside atomic() that has side effects (API calls, file writes) still executes even if transaction rolls back — use on_commit(). Durable=True prevents nesting.
The GIL (Global Interpreter Lock) allows only one thread to execute Python bytecode at a time, limiting CPU-bound parallelism.
The GIL prevents multiple threads from executing Python code simultaneously in CPython. Impact: CPU-bound tasks don't benefit from threading. Solutions: multiprocessing (separate processes, each with own GIL), C extensions (release GIL during computation), asyncio for I/O-bound work. I/O-bound tasks (network, file) release GIL while waiting — threading works fine. Django: use Celery for CPU-intensive tasks, asyncio views for I/O. Python 3.12 introduced sub-interpreters; 3.13 has free-threaded mode (no-GIL experimental).
Middleware processes requests/responses globally through a chain of components, each wrapping the next in order.
Request flows through MIDDLEWARE list top-to-bottom, response flows bottom-to-top. Each middleware can: process request before view (authentication, CSRF), process response after view (add headers, compress), handle exceptions. Methods: __init__ (one-time setup), __call__ (called per request). process_view() runs after URL resolution. process_exception() handles unhandled exceptions. process_template_response() modifies template responses. Order matters: SecurityMiddleware first, SessionMiddleware before AuthenticationMiddleware. Custom middleware for logging, rate limiting, request ID tracking.
Profile queries with Django Debug Toolbar, add database indexes, use caching, optimize ORM queries, and add pagination.
Steps: (1) Django Debug Toolbar — identify slow queries and N+1 problems. (2) ORM: select_related/prefetch_related, only/defer, values/values_list. (3) Database indexes: db_index=True, Meta.indexes. (4) Caching: per-view cache, template fragment cache, Redis/Memcached for sessions. (5) Pagination for large querysets. (6) Database connection pooling (pgbouncer). (7) Async views for I/O-bound endpoints. (8) Static file serving via CDN. (9) Celery for background tasks. (10) Monitor with django-silk or New Relic.
Use Django REST Framework with JWT or Token authentication, serializers for validation, and viewsets for CRUD.
Setup: (1) Install djangorestframework, djangorestframework-simplejwt. (2) Define serializers (ModelSerializer for auto-fields). (3) Create viewsets (ModelViewSet for full CRUD). (4) Register with router for URL generation. (5) JWT auth: obtain token pair on login, access token in Authorization header. (6) Permission classes: IsAuthenticated, IsAdminUser, custom permissions. (7) Throttling: AnonRateThrottle, UserRateThrottle. (8) Pagination: PageNumberPagination. (9) Filtering: django-filter integration. (10) API documentation: drf-spectacular for OpenAPI.
Use RunSQL for complex operations, create migrations in small steps, test on staging, and use zero-downtime techniques.
Strategies: (1) Small, focused migrations — one change per migration. (2) AddField with null=True first, then data migration, then alter to not null. (3) RunSQL for raw SQL when ORM is too slow. (4) SeparateDatabaseAndState for renaming without data movement. (5) Zero-downtime: never remove columns in same deploy as code — deploy code first, then migrate. (6) django-pg-zero-downtime-migrations for PostgreSQL. (7) Test on staging with production-size data. Instagram uses Django at massive scale with custom sharding.
Context managers use with statement for resource management, ensuring cleanup via __enter__ and __exit__ methods.
Protocol: __enter__() runs on entering with block, __exit__(exc_type, exc_val, exc_tb) runs on exit (even with exceptions). Common: open() for files, threading.Lock(), database connections. contextlib: @contextmanager decorator simplifies creation using yield. contextlib.suppress() ignores specific exceptions. ExitStack manages multiple context managers dynamically. Django: transaction.atomic() is a context manager. Use for: file handling, locks, database sessions, temporary state changes, timing blocks.
Django async views use async def and await for non-blocking I/O operations like external API calls and database queries.
Django 3.1+ supports async def views that run in asyncio event loop. Use for: external API calls (aiohttp), WebSocket handling (channels), multiple concurrent I/O operations. async for with ORM (Django 4.1+): async for obj in Model.objects.all(). ASGI deployment required (uvicorn, daphne). Limitations: ORM is sync by default (use sync_to_async wrapper or async ORM methods). Don't use for CPU-bound work. Mix sync and async views in same project. Channels for WebSocket, long-polling, server-sent events.
Use TestCase for database tests, SimpleTestCase for non-DB, fixtures or factory_boy for data, and test views, models, and forms.
Django TestCase wraps each test in transaction (auto-rollback). Use setUp()/setUpTestData() for test data. factory_boy > fixtures for flexible test data. Test layers: Unit (models, utils), Integration (views, forms), E2E (Selenium/Playwright). Client() simulates requests: self.client.get('/api/'). assertContains, assertRedirects, assertTemplateUsed. Mock external services with unittest.mock. Coverage: coverage.py with --branch. Performance: --parallel flag, in-memory SQLite for speed. CI: run tests before every merge. Django's test runner auto-discovers test*.py files.
Ready to master Python django?
Start learning with our comprehensive course and practice these questions.