Technical interviews can feel scary, especially when the questions use words you've only half heard before. This post takes six real Python and Django interview questions and explains each one twice:
- The model answer — short and confident, what you'd actually say in the interview.
- The beginner explanation — slower, with everyday examples, so you truly understand it (interviewers can tell when you've only memorised).
Tip: read the beginner explanation first, then practise saying the model answer out loud in your own words.
1. Module vs package
Model answer: A module is a single .py file you can import. A package is a folder that groups related modules, usually marked by an __init__.py file.
Beginner explanation. Think of a library:
- A module is one book — one file such as
utils.py with some functions in it.
- A package is a shelf of books — a folder such as
data_processing/ holding several modules.
my_project/
├── utils.py ← a module (one file)
└── data_processing/ ← a package (a folder)
├── __init__.py
├── cleaners.py ← module inside the package
└── validators.py ← module inside the package
# Importing a module
import utils
from utils import some_function
# Importing from a package
import data_processing.cleaners
from data_processing import validators
In Django: every app you create (python manage.py startapp blog) is a package — a folder with __init__.py, models.py, views.py and so on.
2. What is __init__.py for?
Model answer: It marks a folder as a regular package, runs once when the package is first imported (so it can hold setup code), and can control what from package import * exposes through __all__.
Beginner explanation. __init__.py is like the reception desk of a building:
- It tells visitors "this is an official building" (Python treats the folder as a package).
- Anything written there runs the first time someone enters (the first import).
- It can decide which rooms are shown on the guided tour when someone does
from package import *.
# data_processing/__init__.py
from .cleaners import clean_text # shortcut: now `from data_processing import clean_text` works
__all__ = ['clean_text'] # what `from data_processing import *` gives you
It can also be completely empty — that's normal and very common.
Bonus point for the interview: since Python 3.3, a folder without __init__.py can still be imported as a "namespace package". Regular packages (and Django apps) still include it, because it makes the intent clear and avoids surprises.
3. The many uses of the underscore _
Model answer: _ is used as a throwaway variable, a "for internal use" naming convention, the last result in the interactive shell, an alias for translation functions, and as a digit separator in numbers like 1_000_000.
Beginner explanation. The humble underscore has five jobs:
# 1. "I don't need this value" — a throwaway variable
name, _, age = ("John", "middle", 25)
for _ in range(3):
print("Hello") # we don't care about the loop counter
# 2. "Please don't touch, this is internal" — a naming convention
class BankAccount:
def _calculate_interest(self): # meant for use inside the class only
...
# Python won't stop you calling it — it's a polite "staff only" sign, not a locked door.
# 3. The last answer in the interactive Python shell
# >>> 5 + 3
# 8
# >>> _ * 2
# 16
# 4. Translations (making your app multilingual)
from django.utils.translation import gettext as _
print(_("Hello")) # shows "Bonjour" for French users, once translated
# 5. Making big numbers readable (Python 3.6+)
population = 34_000_000 # same as 34000000
Follow-up you might get: "What about double underscores like __balance?" — two leading underscores trigger name mangling: Python renames it to _BankAccount__balance so subclasses don't accidentally overwrite it. Names with underscores on both sides, like __init__, are special "dunder" methods Python calls for you.
4. @staticmethod vs @classmethod
Model answer: A static method receives no automatic first argument — it's a plain function that lives inside the class for organisation. A class method receives the class itself as cls, so it can read class data or create new objects — it's commonly used for alternative constructors.
Beginner explanation. Picture a school:
- A normal method (with
self) works with one particular student — "what is Ama's age?"
- A class method (with
cls) works with the school as a whole — "what's the school's name?" or "enrol a new student from this form".
- A static method is a general rule pinned on the notice board — "is 18 old enough to be an adult?" It doesn't need to know about any student or the school.
class Person:
species = "Homo sapiens" # shared by every Person
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self): # normal method: needs one person (self)
return f"Hi, I'm {self.name}"
@staticmethod
def is_adult(age): # no self, no cls — just a helper
return age >= 18
@classmethod
def get_species(cls): # gets the class as cls
return cls.species
@classmethod
def from_string(cls, text): # an "alternative constructor"
name, age = text.split('-')
return cls(name, int(age)) # builds a new Person
Person.is_adult(25) # True
Person.get_species() # 'Homo sapiens'
bob = Person.from_string("Bob-25") # a Person named Bob, aged 25
Why cls and not just Person? If you later write class Student(Person), then Student.from_string("Ama-20") correctly creates a Student, because cls is whichever class called it. That's the detail that impresses interviewers.
5. Getting data from Python to a D3.js chart
Model answer: Convert the data to JSON, then either serve it from an API endpoint that D3 fetches with d3.json(), or embed it safely in the page with Django's json_script filter.
Beginner explanation. Python and JavaScript speak different languages. JSON is the shared language both understand — a simple text format of lists and labels. Python packs the data into JSON, and D3.js (a JavaScript library for drawing charts) unpacks it and draws.
Option A — an API endpoint (best when the chart updates or the data is large):
# views.py
from django.http import JsonResponse
def sales_data(request):
data = [
{'name': 'Alice', 'value': 30},
{'name': 'Bob', 'value': 45},
{'name': 'Charlie', 'value': 25},
]
return JsonResponse(data, safe=False) # safe=False lets us send a list
// In the page — fetch the data, then draw one bar per item
d3.json('/api/sales/').then((data) => {
d3.select('svg')
.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (d, i) => i * 40)
.attr('width', 30)
.attr('height', (d) => d.value * 3);
});
Option B — put the data straight into the page (best for small data shown once):
{{ chart_data|json_script:"chart-data" }}
<script>
const data = JSON.parse(document.getElementById('chart-data').textContent);
// ...draw with D3 as above
</script>
json_script is the safe way to do this — it escapes the data so nobody can sneak a script in through it (see XSS in our Django security guide).
If you work with pandas, df.to_dict('records') turns a table into exactly this list-of-dictionaries shape.
6. What is Django middleware?
Model answer: Middleware is a chain of hooks that every request passes through on the way in and every response passes through on the way out. It handles things that apply to the whole site — security headers, sessions, authentication, CSRF, logging, caching.
Beginner explanation. Imagine entering an office building:
- Security guard checks your ID.
- Reception signs you in.
- You reach the office you came for (that's the view — the code for one page).
- On the way out, you pass reception and security again in reverse order.
Middleware are those checkpoints. Instead of adding the same checks to every page, you write them once, and they apply everywhere.
# myapp/middleware.py
import time
class TimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response # the next checkpoint (or the view)
def __call__(self, request):
start = time.time() # ← on the way IN
response = self.get_response(request) # let the request continue
duration = time.time() - start # ← on the way OUT
response['X-Page-Time'] = f'{duration:.3f}s'
return response
# settings.py — add it to the list (order matters!)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# ...
'myapp.middleware.TimingMiddleware',
]
Why order matters: requests go through the list top to bottom, responses bottom to top. For example, AuthenticationMiddleware must come after SessionMiddleware, because it needs the session to know who is logged in.
Common uses: authentication, CSRF protection, security headers, CORS, logging, caching, and blocking unwanted IP addresses.
Quick-fire practice
Cover the answer, say yours out loud, then check.
Is a Django app a module or a package?
A package — it's a folder with __init__.py and several modules (models.py, views.py, …).
What does `for _ in range(5)` tell another developer?
That the loop variable isn't used — we only want to repeat something five times.
Which decorator would you use for an alternative constructor, and why?
@classmethod, because it receives cls and can call cls(...) to build the object — and it still works correctly for subclasses.
What's the safest way to put Python data into a template for JavaScript?
The json_script template filter — it outputs the data as JSON inside a <script type="application/json"> tag with special characters escaped.
In which order does middleware handle the response?
Bottom to top — the reverse of the order in the MIDDLEWARE setting.
Read next: Django security explained simply — 11 ways to protect your app.
Based on the Python technical interview cheat sheet. Preparing for a developer role or need mentoring? Get in touch with SammyFold.
AS
Agyemang SamuelSammyFold Technology · Building software for African businesses. Got a question about this post? Use the Chat tab or contact us.