25 Django REST Framework interview questions, with real answers

Most DRF interview question lists are flashcards — “what is a serializer?” with a one-line answer copied from the docs. Nobody gets hired by reciting those. Interviewers use them as an opening and then ask a follow-up, and the follow-up is the actual test.

What follows is organised the way interviews actually go: the question, a real answer, and the follow-up you should expect.

Serializers

1. What does a serializer actually do?

Two jobs, in both directions. It converts querysets and model instances into native Python types that can be rendered as JSON, and it validates and converts incoming data back into objects. Calling it “a converter” misses the validation half, which is where most of the work is.

2. Serializer vs ModelSerializer?

ModelSerializer generates fields and basic validators from a model, and gives you default create() and update(). Use it when your payload maps closely to a model. Drop to plain Serializer when it does not — aggregated responses, payloads spanning several models, or anything not backed by a model at all.

Follow-up: “What happens if you need one extra computed field?” — SerializerMethodField, and it is read-only.

3. Where do you put validation, and why there?

Three levels, and knowing when to use which is the real question:

  • validate_<field>() for a single field in isolation.
  • validate() when a rule spans two fields — end date after start date.
  • Model-level clean() or database constraints for invariants that must hold no matter what wrote the row.

The point worth making: serializer validation protects the API, database constraints protect the data. A management command or the admin bypasses the serializer entirely.

4. How do you handle nested writes?

Nested serializers are read-only by default. For writes you override create() / update() and handle the child objects yourself, inside a transaction. Many teams deliberately avoid nested writes and use separate endpoints instead, because partial-update semantics on nested collections get ambiguous fast.

5. What is the N+1 problem in a DRF list endpoint?

A serializer with a nested relation triggers a query per object. One hundred objects, one hundred and one queries. Fix it in the view’s queryset with select_related() for forward foreign keys and prefetch_related() for reverse and many-to-many.

queryset = (
    Order.objects
    .select_related("customer")
    .prefetch_related("items__product")
)

This one comes up constantly, because it is the most common real performance bug in DRF codebases.

Views, ViewSets and routers

6. APIView vs generics vs ViewSet?

Increasing abstraction. APIView gives you HTTP method handlers and nothing else. Generic views add the standard list / create / retrieve / update / destroy behaviour. ViewSet groups related actions into one class so a router can generate the URLs.

Follow-up: “When would you go back to APIView?” — when the endpoint is not CRUD-shaped. Forcing a report or a bulk action through a ModelViewSet produces worse code than writing it plainly.

7. What does a router actually give you?

URL generation and naming conventions, and consistency across a large API. The cost is indirection — the URLs are not written anywhere you can read them. @action(detail=True) adds custom routes to a ViewSet.

8. get_queryset() vs the queryset attribute?

The class attribute is evaluated once at import. get_queryset() runs per request, so it is where anything request-dependent belongs — filtering by the current user, by a URL parameter, by tenant. Filtering by self.request.user in a class attribute is a real bug that leaks other users’ data.

Authentication and permissions

9. Authentication vs permissions?

Authentication answers “who is this request from?” and sets request.user. Permissions answer “is this user allowed to do this?” They run in that order, and conflating them is a classic junior mistake.

10. Token vs session vs JWT?

Session auth uses a server-side session and a cookie; fine when the client is a browser on the same domain, and it needs CSRF protection. DRF’sTokenAuthentication issues one long-lived database token per user — simple, revocable, but a database lookup per request. JWT is stateless and self-contained, so it scales without shared session storage.

11. So what is the catch with JWT?

Revocation. A stateless token is valid until it expires, so you cannot truly log someone out. The mitigations are short-lived access tokens with refresh tokens, and a blocklist for refresh tokens — at which point you have reintroduced the server-side state JWT was supposed to remove. Saying this out loud is the answer that separates people who have used JWT from people who have read about it.

Follow-up: “Where do you store it on the client?” —localStorage is XSS-exposed; an httpOnly cookie is safer but brings CSRF back. There is no free option, only a chosen trade-off.

12. How do you write a custom permission?

Subclass BasePermission. has_permission() for the view as a whole, has_object_permission() for a specific instance. The trap: has_object_permission is not called on list endpoints, so object-level filtering belongs in get_queryset().

class IsOwner(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.owner_id == request.user.id

13. What is throttling for?

Rate limiting. AnonRateThrottle and UserRateThrottle cover the common cases; ScopedRateThrottle lets you apply a tighter limit to expensive or sensitive endpoints such as login or password reset. Worth saying: throttling is not a security control on its own, it raises the cost of abuse.

Practical API design

14. How do you version an API?

URL path versioning (/api/v1/) is the most common because it is visible and cacheable. Header and query-parameter versioning exist and are tidier in theory. The real answer is that you should have a deprecation policy before you need a v2, not after.

15. Pagination styles?

PageNumberPagination is simplest. LimitOffsetPagination suits clients that want arbitrary windows. CursorPagination is the correct choice for large or frequently-changing datasets, because offset pagination silently skips or repeats rows when items are inserted between requests.

16. Which status codes, and when?

  • 200 success, 201 created, 204 deleted with no body
  • 400 validation failure, 401 not authenticated, 403 authenticated but not allowed
  • 404 not found — and sometimes deliberately instead of 403, so you do not leak that a resource exists
  • 409 conflict, 429 throttled

The 401 vs 403 distinction gets asked a lot and answered wrong a lot.

17. PUT vs PATCH?

PUT replaces the resource, PATCH updates part of it. In DRF that is partial=True. A genuine PUT should require every writable field, and omitting one should blank it — which is why most APIs in practice use PATCH for everything.

18. How do you document the API?

drf-spectacular generates an OpenAPI 3 schema from your serializers and views, served as Swagger UI or Redoc. The honest caveat: generated docs are only as good as your serializers, and anything dynamic needs explicit @extend_schema annotations.

19. Filtering and search?

django-filter for structured field filtering, SearchFilter for simple text search, OrderingFilter for sorting. Say out loud that any filterable field needs a database index, or your filtering endpoint becomes a table scan.

Testing and production

20. How do you test a DRF endpoint?

APIClient with force_authenticate() to skip the login round trip, asserting on both status code and response body. Test the permission failures, not only the happy path — the test that matters is the one proving user A cannot read user B’s data.

def test_other_user_cannot_read(api_client, alice, bobs_order):
    api_client.force_authenticate(alice)
    response = api_client.get(f"/api/v1/orders/{bobs_order.id}/")
    assert response.status_code == 404

21. What breaks when you move to production?

DEBUG=False and ALLOWED_HOSTS, static files needing a real server or WhiteNoise, secrets moving to environment variables, database connection pooling, and CORS. Every one of those has bitten someone on their first deploy.

22. How do you handle long-running work?

Not in the request. Hand it to Celery with Redis or RabbitMQ and return 202 Accepted with a way to check status. Sending email synchronously inside a view is the canonical example of what not to do.

23. SELECT FOR UPDATE — when?

When two concurrent requests could read the same row, both decide it is safe to act, and both write. Inventory decrements and balance transfers are the standard examples. select_for_update() inside transaction.atomic() takes a row lock.

24. How would you cache a read-heavy endpoint?

Fix the queries first — most “we need caching” problems are N+1 problems. After that, per-view caching with a Redis backend and a key that includes the user where responses differ per user. Then talk about invalidation, because that is the hard half.

25. What would you do differently on your last project?

Not a technical question, but it is almost always asked and it is where candidates fall apart. Have a real answer about a specific decision that did not work out and what you learned. “Nothing” reads as no self-awareness.

How to prepare, honestly

You cannot memorise your way through the follow-ups. Build one API with authentication, permissions, pagination, filtering, tests and documentation, deploy it, and you will have lived most of the questions above. The answers then come out as experience rather than recall, which interviewers can tell apart instantly.

That is exactly what phase four of the Python & Django backend course builds. If you are still deciding on a stack, the full stack vs backend post is worth reading first.

Chat on WhatsApp