Django / DRF / Backend — Interview
Notes
Q1. What is a Serializer?
A Serializer in DRF converts complex Python objects (like model instances or querysets) into JSON so they can be
sent to the client. It also converts incoming JSON into Python objects and validates the data before it's used.
A serializer acts as a bridge between Python objects and JSON. It is responsible for serialization, deserialization, and
validating incoming request data.
Q2. How do we perform validation in a Serializer?
Validation happens when we call:
serializer.is_valid()
DRF provides three main ways to validate data:
1. Field-level validation
Validates a single field.
Example:
validate_email(self, value)
Used when you want to validate only the email field.
2. Object-level validation
Validates multiple fields together.
Example:
validate(self, attrs)
Used when validation depends on more than one field, such as checking that start_date is before end_date.
3. Built-in validators
You can use validators like:
• Required fields
• Maximum length
• Minimum length
• Email format
• Unique validation (especially with ModelSerializer)
Q3. What is the difference between Serializer and
ModelSerializer?
Serializer
• You define every field manually.
• You write create() and update() methods yourself.
• Best when you're not directly working with a Django model or need full control.
ModelSerializer
• Automatically generates fields from the Django model.
• Automatically provides create() and update().
• Can automatically apply model validations like unique constraints.
• Requires much less code.
Q4. How do you implement database validation?
I implement validation in different layers. First, I validate the request in the serializer using is_valid(). Then, at the
database level, I add constraints like Unique, Not Null, Foreign Key, and Check Constraints to protect the data.
If a new validation rule is needed later, I update the model and create a migration so the database schema is updated.
If some validation or action must happen automatically whenever data is inserted, updated, or deleted, I can use a
database trigger.
For NoSQL databases, since they have fewer built-in constraints, I usually implement most validation in the
application, although schema validation can also be configured.
For example, if email must be unique, I add a unique constraint in the database. If later the business says phone
number should also be unique, I update the model, generate a migration, and apply it. After that, the database itself
won't allow duplicate phone numbers.
Q5. ORM and Raw SQL Queries — Difference
ORM
An ORM is a programming technique and tool that wraps a database in an abstraction layer. Instead of managing
tables and rows, you interact with data as native code classes and objects.
Raw SQL
Raw SQL queries mean writing database queries manually using SQL instead of using an ORM. In this approach,
we directly send SQL statements like SELECT, INSERT, UPDATE, and DELETE to the database.
Q6. Why is ORM slower than raw SQL?
Because ORM adds an abstraction layer. It has to build the SQL query, map the results to Python objects, and
sometimes generates less optimized queries. Raw SQL lets me write exactly the query I want, so for complex
operations it can be faster.
Q7. What is the N+1 Problem?
The N+1 problem happens when our code executes one query to get a list of records, and then for each record it
executes an additional query to fetch related data. So instead of 1 query, it becomes 1 + N queries, which causes
performance issues.
Solution
We solve the N+1 problem using query optimization techniques like select_related and prefetch_related in Django
ORM.
1. select_related
Used for ForeignKey and One-to-One relationships. It performs a SQL JOIN and fetches related data in a single
query.
Example: The waiter brings your burger and fries together on the same plate, at the same time.
2. prefetch_related
Used for Many-to-Many or reverse relationships. It runs separate optimized queries but reduces repeated queries by
fetching related data in bulk.
Example: The waiter brings your burger first. Then, they go back to the kitchen to fetch a giant basket of fries for
the whole table to share.
Q8. If an API is crashing in production and you haven't pushed
any changes, how would you debug it?
If an API is crashing in production and I didn't push any changes, first I check the logs to see the exact error. Logs
usually tell me what is breaking and where.
Then I check server health like CPU, memory, and database status to see if it's a load or resource issue.
After that, I try to reproduce the issue in staging or local with the same request.
I also check external services like database, Redis, or third-party APIs.
If the issue is serious, I may temporarily disable the endpoint or rollback to keep the system stable while I fix it.
Q9. Pagination
Pagination means splitting large data into smaller chunks or pages instead of sending everything at once in a single
API response.
How it works
We send a limited number of records per request, like 10 or 20 items per page. The client can request next pages
using page number or offset.
Q10. Rate Limiting and Its Status Code
Rate limiting means controlling how many requests a user or client can make to an API within a specific time
period.
When the rate limit is exceeded, the server returns HTTP status code 429 Too Many Requests.
Q11. 500–599 Status Codes
5xx status codes mean the error is from the server side. It means the request was valid from the client, but the server
failed to process it due to some internal issue.
Q12. JWT — How It Works
JWT (JSON Web Token) is a token-based authentication system used to securely identify a user without storing
session data on the server.
How it works, step by step
• When a user logs in, the server verifies the credentials and generates a JWT token. This token contains
encoded user information and is signed using a secret key.
• The server sends this token back to the client, and the client stores it (usually in local storage or cookies).
• For every next request, the client sends this token in the Authorization header.
• The server then verifies the token signature using the secret key. If it is valid, the user is authenticated and
allowed to access the API.
Q13. Session Authentication — How It Works
Session-based authentication is a method where the server stores user login information on the server side and
maintains a session for each logged-in user.
How it works, step by step
• When a user logs in, the server verifies the credentials and creates a session for that user on the server.
• The server generates a session ID and sends it to the client, usually stored in a cookie.
• For every next request, the client sends this session ID back to the server.
• The server checks this session ID in its session store. If it is valid, the user is authenticated.
Q14. OAuth
OAuth is an authorization framework that allows a user to give limited access to their data on one application to
another application, without sharing their password.
How it works
You click 'Login with Google' → Google asks permission → after approval, Google gives a token to the app → the
app uses that token to get your basic info.
Why we use it
We use OAuth so users don't need to create a new password and we don't handle their password directly.
Q15. Database Optimization
Database optimization means improving database performance so queries run faster and the system uses fewer
resources like CPU, memory, and disk I/O.
How we do it
• Use indexing — add indexes on columns used in WHERE, JOIN, ORDER BY
• Select only required data — avoid SELECT *, fetch only needed fields
• Optimize queries — reduce joins and unnecessary subqueries; write simple and efficient queries
• Fix the N+1 problem — use select_related (FK / One-to-One) and prefetch_related (Many-to-Many /
reverse relations)
• Use pagination — don't load large datasets in one response
• Use caching — store frequently used data in Redis / a cache layer
• Analyze slow queries — use EXPLAIN / a query analyzer to check query cost
• Database normalization (when needed) — avoid duplicate data, but balance with performance
Q16. Transaction
A transaction is a group of database operations that are treated as a single unit. Either all operations succeed, or if
one fails, all changes are rolled back.
Q17. ACID Properties
ACID is a set of properties that ensures database transactions are reliable and keep data consistent.
1. Atomicity
Atomicity means all operations in a transaction succeed together, or if one fails, everything is rolled back. Example:
bank transfer.
2. Consistency
Consistency means the database always stays in a valid state before and after a transaction. Example: data should
follow all constraints and rules.
3. Isolation
Isolation means multiple transactions can run at the same time without affecting each other. Example: two users
updating different records shouldn't interfere.
4. Durability
Durability means once a transaction is committed, the data is permanently saved, even if the server crashes.
One-line summary: ACID ensures transactions are safe, reliable, and keep the database consistent.
Q18. SOLID Principles
SOLID is a set of design principles that helps us write clean, maintainable, and scalable code.
1. Single Responsibility Principle (SRP)
A class should have only one responsibility or one reason to change. Example: a UserService should handle users
only, not send emails.
2. Open/Closed Principle (OCP)
Code should be open for extension but closed for modification. Example: add a new payment method by creating a
new class instead of changing existing code.
3. Liskov Substitution Principle (LSP)
A child class should be able to replace its parent class without breaking the application. Example: if a class extends
another class, it should still behave correctly wherever the parent is used.
4. Interface Segregation Principle (ISP)
A class should not be forced to implement methods it doesn't need. Example: create small, specific interfaces instead
of one large interface.
5. Dependency Inversion Principle (DIP)
High-level modules should depend on abstractions, not concrete implementations. Example: depend on an interface
instead of directly depending on a specific database class.
Q19. DELETE, TRUNCATE, and DROP
DELETE is used when I want to remove specific records from a table, and I can use a WHERE condition.
TRUNCATE is used when I want to quickly remove all records but keep the table structure. DROP is used when I
no longer need the table because it deletes both the data and the table structure.
Q20. What is Normalization?
Normalization is the process of organizing data in a database to reduce duplicate data and maintain data consistency.
1NF (First Normal Form)
In First Normal Form, each column should contain only one value, and each row should be unique.
❌✅
Example:
Skills = Python, Java
Create separate rows:
Aneeq | Python
Aneeq | Java
2NF (Second Normal Form)
Second Normal Form means the table should already be in 1NF, and every non-key column should depend on the
whole primary key, not just part of it.
Simple example: suppose the primary key is (StudentID, CourseID). If StudentName depends only on StudentID, it
should be moved to a separate Student table.
3NF (Third Normal Form)
Third Normal Form means the table should already be in 2NF, and non-key columns should not depend on other
non-key columns.
Simple example: instead of storing Employee → Department → Department Manager, if the Department Manager
depends on Department, not on Employee, move department information into a separate Department table.
Easy way to remember
• 1NF → One value per column (no repeating values).
• 2NF → Every column depends on the whole primary key.
• 3NF → Columns should depend only on the primary key, not on another non-key column.
Q21. Constructor
A constructor is a special method that is called automatically when an object is created. We use it to initialize the
object's data or set up its initial state.
In Python
The constructor is __init__(). Simple example: when I create a User object, the constructor can automatically set
values like name and email.
Q22. Destructor
A destructor is a special method that is called when an object is about to be destroyed. We use it to clean up
resources, such as closing files or database connections if needed.
In Python
The destructor is __del__().
Q23. Pure Virtual Function
A pure virtual function has no implementation in the base class, and every derived class must implement it.
Q24. Synchronous Programming
In synchronous programming, tasks are executed one after another. The next task starts only after the previous task
finishes.
Example: if one API call takes 5 seconds, the program waits those 5 seconds before executing the next task.
Q25. Asynchronous Programming
In asynchronous programming, the program doesn't wait for one task to finish. While one task is waiting, it can
execute other tasks, making the application more efficient.
Example: if an API call is waiting for a response, the program can process another request instead of staying idle.
Q26. await
await is used inside an async function. It pauses only that function until the awaited task completes, while allowing
other asynchronous tasks to continue running.
Q27. I/O-bound Process
An I/O-bound process is a task that spends most of its time waiting for input or output operations instead of using
the CPU.
Q28. Thread Pool
A thread pool is a collection of reusable threads. Instead of creating a new thread for every task, we reuse existing
threads to execute multiple tasks.
How it works
When a task comes in, it is assigned to an available thread in the pool. After the thread finishes the task, it returns to
the pool and waits for the next task.
Q29. *args and **kwargs
Normally, you write:
add(a, b)
It expects exactly 2 values. If you pass 3 values, it gives an error.
*args
Now suppose you don't know how many values the user will pass — sometimes they pass 2 numbers, sometimes 5,
sometimes 10. Instead of fixing the number of parameters, you use *args. Python will collect all positional values
into one tuple.
Example: passing 1, 2, 3, 4 as *args becomes the tuple (1, 2, 3, 4).
So *args means: collect all extra positional values and keep them in a tuple.
**kwargs
Now instead of values, suppose the user sends name=value pairs, such as name="Aneeq", age=23, city="Lahore".
Python collects them into a dictionary:
{ "name": "Aneeq", "age": 23, "city": "Lahore" }
So **kwargs means: collect all keyword arguments into a dictionary.
Q30. Recursion
Recursion is a programming technique where a function calls itself to solve a problem. Each call works on a smaller
part of the problem until it reaches a stopping condition, called the base case.
Q31. Lambda Function
A lambda function is a small function without a name. We use it when we need a simple function only once, instead
of creating a separate function with def.
Q32. Garbage Collection
Garbage collection is the process of automatically freeing memory by removing objects that are no longer being
used by the program.
Q33. __init__
__init__ is the constructor in Python. It is automatically called when an object is created.
Q34. self
self represents the current object (instance) of a class.
Q35. Multithreading
Multithreading means one process has multiple threads working together. If one thread is waiting for something like
a database or API response, another thread can continue working. That's why it's best for I/O-bound tasks.
Examples
• API calls
• Database queries
• File reading/writing
Q36. Multiprocessing
Multiprocessing means running multiple independent processes. Each process has its own memory and can use a
separate CPU core, so it's best for heavy calculations.
Examples
• Image processing
• Video rendering
• Machine learning
• Large calculations
Easy difference
If the program is mostly waiting, I use multithreading. If the program is mostly calculating, I use multiprocessing.
Q37. API Versioning
API versioning means creating different versions of an API so we can make changes without breaking existing
clients that are already using the older version.
Example
Suppose I have a mobile app that calls /api/v1/profile and it returns the user's name and email.
v1 Response:
{ "name": "Aneeq", "email": "aneeq@[Link]" }
Later, the business asks me to change the response by replacing name with first_name and last_name. This is a
breaking change because old mobile apps still expect name. Instead of changing v1, I create v2.
v2 Response:
{ "first_name": "Muhammad", "last_name": "Aneeq", "email":
"aneeq@[Link]" }
Interview Answer
Old mobile apps continue using /api/v1/profile, while new apps use /api/v2/profile. This way, existing users are not
affected, and we can safely introduce breaking changes.
Q38. CORS
CORS stands for Cross-Origin Resource Sharing. It is a browser security mechanism that controls whether a website
can make requests to another domain.