Skip to main content

Command Palette

Search for a command to run...

Senior Python Backend Developer Interview Guide

Published
โ€ข12 min readโ€ขView as Markdown
Senior Python Backend Developer Interview Guide
S

Software Engineer. Exploring and learning the new technologies.

๐Ÿ Python Core Concepts

1. Object-Oriented Programming (OOP)

Classes and Inheritance

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def __str__(self):
        return f"{self.name} is a {self.species}"

    def __repr__(self):
        return f"Animal(name={self.name!r}, species={self.species!r})"

    def speak(self):
        pass

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name, "Dog")
        self.breed = breed

    def speak(self):  # Polymorphism
        return "Woof!"

Key Interview Points:

  • __init__: Constructor for initialization

  • __str__: Human-readable string (for end users)

  • __repr__: Unambiguous representation (for developers/debugging)

  • Polymorphism: Same method name, different implementations

  • Inheritance: Code reuse through parent-child relationships

2. Decorators & Context Managers

Decorators

import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f}s")
        return result
    return wrapper

@timing_decorator
def slow_function():
    time.sleep(2)
    return "Done"

Context Managers

# Using 'with' statement
with open('file.txt', 'r') as f:
    data = f.read()
# File automatically closed

# Custom context manager
class DatabaseConnection:
    def __enter__(self):
        self.conn = connect_to_db()
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()
        return False  # Propagate exceptions

Why use them:

  • Decorators: Add functionality without modifying original code (logging, auth, caching)

  • Context Managers: Ensure proper resource management (files, connections, locks)

3. Iterators & Generators

Iterators

class Counter:
    def __init__(self, max_val):
        self.max_val = max_val
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.max_val:
            raise StopIteration
        self.current += 1
        return self.current

Generators (More Efficient)

def counter(max_val):
    current = 0
    while current < max_val:
        current += 1
        yield current  # Returns value and pauses execution

# Memory efficient for large datasets
numbers = counter(1000000)  # Doesn't create list in memory

Interview Insight: Generators use lazy evaluation, saving memory for large datasets. They're ideal for processing streams or large files.

4. Async/Await

import asyncio
import aiohttp

async def fetch_data(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()

async def main():
    urls = ['http://api1.com', 'http://api2.com', 'http://api3.com']
    tasks = [fetch_data(url) for url in urls]
    results = await asyncio.gather(*tasks)  # Concurrent execution
    return results

# Run event loop
asyncio.run(main())

Key Concepts:

  • Event Loop: Manages async task execution

  • Non-blocking I/O: Program doesn't wait for I/O operations

  • Concurrency vs Parallelism: Async handles I/O-bound tasks efficiently

5. Global Interpreter Lock (GIL)

What is GIL?

  • Python's mutex preventing multiple threads from executing Python bytecode simultaneously

  • Impact: Multi-threading doesn't provide true parallelism for CPU-bound tasks

When it matters:

  • โŒ CPU-bound tasks: Use multiprocessing instead

  • โœ… I/O-bound tasks: Threading works fine (GIL released during I/O)

# For CPU-bound tasks
from multiprocessing import Pool

def cpu_intensive(x):
    return sum(i*i for i in range(x))

with Pool(4) as p:
    results = p.map(cpu_intensive, [10000000] * 4)

๐ŸŒ Django Framework

1. MVC Architecture (MTV in Django)

Models (Data Layer)

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey('User', on_delete=models.CASCADE)

    class Meta:
        ordering = ['-created_at']
        indexes = [models.Index(fields=['created_at'])]

Views (Logic Layer)

from django.shortcuts import render
from .models import Post

def post_list(request):
    posts = Post.objects.select_related('author').all()
    return render(request, 'posts/list.html', {'posts': posts})

Templates (Presentation Layer)

{% for post in posts %}
    <h2>{{ post.title }}</h2>
    <p>By {{ post.author.username }}</p>
{% endfor %}

2. ORM Query Optimization

select_related vs prefetch_related

# โŒ N+1 Query Problem
posts = Post.objects.all()
for post in posts:
    print(post.author.username)  # Hits DB each time!

# โœ… select_related (for ForeignKey, OneToOne)
posts = Post.objects.select_related('author').all()  # 1 query with JOIN

# โœ… prefetch_related (for ManyToMany, reverse ForeignKey)
posts = Post.objects.prefetch_related('comments').all()  # 2 queries total

Aggregation

from django.db.models import Count, Avg

Post.objects.aggregate(
    total_posts=Count('id'),
    avg_comments=Avg('comments__count')
)

3. Middleware

class TimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start_time = time.time()

        response = self.get_response(request)  # View processing

        duration = time.time() - start_time
        response['X-Request-Duration'] = str(duration)
        return response

Flow: process_request โ†’ View โ†’ process_response

4. Signals

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail

@receiver(post_save, sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
    if created:  # Only for new users
        send_mail(
            'Welcome!',
            'Thanks for joining us.',
            'from@example.com',
            [instance.email]
        )

Use Cases: Email notifications, logging, cache invalidation (decoupled from main logic)

5. Authentication & Permissions

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def protected_view(request):
    return Response({'message': f'Hello {request.user.username}'})

# JWT Token generation
def get_tokens_for_user(user):
    refresh = RefreshToken.for_user(user)
    return {
        'refresh': str(refresh),
        'access': str(refresh.access_token),
    }

๐Ÿงช Flask Framework

1. Blueprints (Modular Routing)

# auth/routes.py
from flask import Blueprint

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/login', methods=['POST'])
def login():
    return {'message': 'Login endpoint'}

# app.py
from flask import Flask
from auth.routes import auth_bp

app = Flask(__name__)
app.register_blueprint(auth_bp)

2. App Factory Pattern

def create_app(config_name='development'):
    app = Flask(__name__)
    app.config.from_object(f'config.{config_name}Config')

    # Initialize extensions
    db.init_app(app)
    migrate.init_app(app, db)
    jwt.init_app(app)

    # Register blueprints
    app.register_blueprint(auth_bp)
    app.register_blueprint(api_bp)

    return app

# Easy testing with different configs
app = create_app('testing')

3. Request Context

from flask import g, request, session

@app.before_request
def load_user():
    g.user = get_current_user()  # Available throughout request

@app.route('/dashboard')
def dashboard():
    user_agent = request.headers.get('User-Agent')
    user_id = session.get('user_id')  # Encrypted cookie
    current_user = g.user  # From before_request
    return render_template('dashboard.html')

โšก FastAPI Framework

1. Why FastAPI is Fast

from fastapi import FastAPI, Depends
from pydantic import BaseModel
from typing import List

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_available: bool = True

@app.post("/items/", response_model=Item)
async def create_item(item: Item):  # Auto validation
    # Async = non-blocking I/O
    await save_to_db(item)
    return item

Speed factors:

  • Async: Handles concurrent requests efficiently

  • Pydantic: Fast data validation using type hints

  • Starlette: High-performance ASGI framework

2. Dependency Injection

from fastapi import Depends
from sqlalchemy.orm import Session

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
async def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user

Benefits: Reusable dependencies for DB connections, auth, logging

3. JWT Authentication

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401)
    except JWTError:
        raise HTTPException(status_code=401)
    return get_user_from_db(username)

@app.get("/protected")
async def protected_route(current_user: User = Depends(get_current_user)):
    return {"user": current_user.username}

4. Background Tasks

from fastapi import BackgroundTasks

def send_email(email: str, message: str):
    # Simulated email sending
    time.sleep(5)
    print(f"Email sent to {email}")

@app.post("/signup")
async def signup(email: str, background_tasks: BackgroundTasks):
    # Response sent immediately
    background_tasks.add_task(send_email, email, "Welcome!")
    return {"message": "Signup successful"}

๐Ÿƒ MongoDB

1. CRUD Operations

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['myapp']
collection = db['users']

# Create
collection.insert_one({'name': 'John', 'age': 30, 'email': 'john@example.com'})

# Read
user = collection.find_one({'name': 'John'})
users = collection.find({'age': {'$gte': 25}})

# Update
collection.update_one(
    {'name': 'John'},
    {'$set': {'age': 31}}
)

# Delete
collection.delete_one({'name': 'John'})

2. Aggregation Pipeline

pipeline = [
    {'$match': {'status': 'active'}},  # Filter
    {'$group': {
        '_id': '$department',
        'avg_salary': {'$avg': '$salary'},
        'count': {'$sum': 1}
    }},
    {'$project': {
        'department': '$_id',
        'avg_salary': 1,
        'count': 1,
        '_id': 0
    }},
    {'$sort': {'avg_salary': -1}}
]

results = collection.aggregate(pipeline)

3. Indexes

# Single field index
collection.create_index('email', unique=True)

# Compound index
collection.create_index([('department', 1), ('salary', -1)])

# Why indexes? Speed up queries dramatically (especially for sorting/filtering)

4. Transactions

with client.start_session() as session:
    with session.start_transaction():
        accounts.update_one({'_id': sender_id}, {'$inc': {'balance': -100}}, session=session)
        accounts.update_one({'_id': receiver_id}, {'$inc': {'balance': 100}}, session=session)
        # Both operations succeed or fail together (ACID)

๐Ÿ—„๏ธ SQL Deep Dive

1. Joins

-- INNER JOIN: Only matching records
SELECT orders.id, customers.name
FROM orders
INNER JOIN customers ON orders.customer_id = customers.id;

-- LEFT JOIN: All from left, matching from right
SELECT customers.name, orders.id
FROM customers
LEFT JOIN orders ON customers.id = orders.customer_id;

-- SELF JOIN: Table joins itself
SELECT e1.name AS employee, e2.name AS manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id;

2. Window Functions

-- Rank employees by salary within each department
SELECT 
    name,
    department,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
    AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;

-- Running total
SELECT 
    date,
    revenue,
    SUM(revenue) OVER (ORDER BY date) as running_total
FROM sales;

3. Common Table Expressions (CTE)

-- Better readability
WITH high_earners AS (
    SELECT * FROM employees WHERE salary > 100000
),
dept_summary AS (
    SELECT department, COUNT(*) as count
    FROM high_earners
    GROUP BY department
)
SELECT * FROM dept_summary WHERE count > 5;

-- Recursive CTE (organizational hierarchy)
WITH RECURSIVE employee_hierarchy AS (
    SELECT id, name, manager_id, 1 as level
    FROM employees WHERE manager_id IS NULL

    UNION ALL

    SELECT e.id, e.name, e.manager_id, eh.level + 1
    FROM employees e
    JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy;

4. Indexes & Performance

-- Primary key index (automatic)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE
);

-- Composite index (for queries filtering on multiple columns)
CREATE INDEX idx_user_location ON users(city, country);

-- When to use: Frequent WHERE, JOIN, ORDER BY operations
-- Trade-off: Faster reads, slower writes

5. ACID Properties

  • Atomicity: All operations in transaction succeed or all fail

  • Consistency: Database remains in valid state

  • Isolation: Concurrent transactions don't interfere

  • Durability: Committed changes persist even after system failure

BEGIN TRANSACTION;
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- Both succeed or both rollback

๐ŸŽฏ Top 25 Interview Questions - Detailed Answers

Python Questions

Q1: Explain decorators

A decorator is a function that takes another function and extends its behavior without explicitly modifying it. It's essentially a wrapper.

def authenticate(func):
    def wrapper(user, *args, **kwargs):
        if not user.is_authenticated:
            raise PermissionError("Authentication required")
        return func(user, *args, **kwargs)
    return wrapper

@authenticate
def view_dashboard(user):
    return f"Welcome {user.name}"

Why use them? Separation of concerns - keep authentication, logging, caching separate from business logic.

Q2: Deep copy vs Shallow copy

import copy

original = [[1, 2], [3, 4]]

shallow = copy.copy(original)
shallow[0][0] = 99  # Modifies original too!

deep = copy.deepcopy(original)
deep[0][0] = 99  # Original unchanged
  • Shallow: Copies object, but references nested objects

  • Deep: Recursively copies everything

Q3: Async vs Threading

  • Async (asyncio): Single-threaded concurrency for I/O-bound tasks. Better for handling many connections.

  • Threading: Multiple threads for I/O-bound tasks. Limited by GIL for CPU-bound work.

  • Multiprocessing: True parallelism for CPU-bound tasks (bypasses GIL).

When to use: Async for web servers/APIs, multiprocessing for data processing.

Django Questions

Q4: select_related vs prefetch_related

# select_related: Uses SQL JOIN (for ForeignKey/OneToOne)
Post.objects.select_related('author')  # 1 query

# prefetch_related: Separate queries + Python join (for ManyToMany)
Post.objects.prefetch_related('tags')  # 2 queries

Q5: When to use Signals?

Signals decouple logic that should happen "as a side effect" of an action:

  • Send welcome email after user creation

  • Clear cache after model update

  • Log audit trail

Avoid using signals for core business logic that should be explicit.

Q6: Middleware flow

Request โ†’ process_request โ†’ View โ†’ process_response โ†’ Response

Each middleware can short-circuit the process (e.g., authentication middleware returning 401).

Flask Questions

Q7: Purpose of Blueprints

Organize large applications into modules:

/auth (login, logout, register)
/blog (posts, comments)
/admin (dashboard, users)

Each blueprint has its own routes, templates, and static files.

Q8: Request context (g, request, session)

  • request: Current HTTP request data (headers, form, args)

  • session: Encrypted cookie for user data across requests

  • g: Global object for storing data during single request (like current user)

Q9: App factory pattern benefits

  • Multiple configurations (dev, test, production)

  • Easy testing with different setups

  • Extension initialization control

  • Blueprint registration in one place

FastAPI Questions

Q10: Why is FastAPI fast?

  1. Async support: Non-blocking I/O for concurrent requests

  2. Pydantic validation: Fast type checking at C-speed

  3. Starlette: High-performance ASGI framework

  4. Automatic docs: No overhead, generated from type hints

Q11: Dependency Injection pattern

async def verify_api_key(api_key: str = Header(...)):
    if api_key != "secret":
        raise HTTPException(status_code=403)
    return api_key

@app.get("/data")
async def get_data(api_key: str = Depends(verify_api_key)):
    return {"data": "sensitive info"}

Reusable, testable, clean code.

Q12: BackgroundTasks usage

Post-response work (email, logging, cleanup):

@app.post("/process")
async def process(background_tasks: BackgroundTasks):
    background_tasks.add_task(heavy_computation)
    return {"status": "processing"}  # Immediate response

Q13: JWT Auth flow

  1. User sends credentials โ†’ /token endpoint

  2. Server validates โ†’ returns JWT token

  3. Client includes token in Authorization: Bearer <token>

  4. Server validates token โ†’ grants access

MongoDB Questions

Q14: When to use aggregation?

Complex queries requiring:

  • Grouping (analytics, reports)

  • Transformations (reshape documents)

  • Multi-stage filtering

  • Calculations across documents

Q15: Index types

  • Single: One field (e.g., email)

  • Compound: Multiple fields (e.g., department + salary)

  • Unique: Enforces uniqueness

  • Text: Full-text search

Q16: Transactions

Multi-document operations that need ACID guarantees:

  • Bank transfers

  • Order processing with inventory updates

  • Any operation where partial success is unacceptable

SQL Questions

Q17: Window functions

Perform calculations across rows related to current row without grouping:

  • Running totals

  • Ranking within partitions

  • Moving averages

  • Lead/lag comparisons

Q18: CTE benefits

  • Readability: Break complex queries into logical steps

  • Recursion: Handle hierarchical data

  • Reusability: Reference same subquery multiple times

Q19: Index strategy

Create indexes on:

  • Primary keys (automatic)

  • Foreign keys (for joins)

  • Columns in WHERE clauses

  • Columns in ORDER BY

  • Composite for multi-column filters

Q20: ACID ensures

Reliable transactions even with:

  • Concurrent users

  • System crashes

  • Network failures

  • Application errors

Backend General Questions

Q21: SQL vs NoSQL

SQL: Relational, structured, ACID, complex queries (financial, ERP) NoSQL: Flexible schema, horizontal scaling, eventual consistency (social media, IoT)

Q22: API Security

  1. Authentication: JWT, OAuth2

  2. HTTPS: Encrypted communication

  3. Rate limiting: Prevent abuse

  4. Input validation: SQL injection, XSS prevention

  5. CORS: Control cross-origin requests

Q23: Dockerize application

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

Benefits: Consistent environments, easy deployment, isolation.

Q24: Testing endpoints

from fastapi.testclient import TestClient

client = TestClient(app)

def test_create_user():
    response = client.post("/users", json={"name": "John"})
    assert response.status_code == 201
    assert response.json()["name"] == "John"

Q25: Scaling strategies

  1. Horizontal: Multiple workers/instances + load balancer

  2. Async I/O: Handle more concurrent connections

  3. Caching: Redis for frequently accessed data

  4. Database optimization: Indexes, query optimization, read replicas

  5. CDN: Static assets

  6. Message queues: Celery for background tasks


๐Ÿ’ก Interview Tips

Code Examples Rule

Always provide code snippets when explaining concepts. Interviewers value practical knowledge over theory.

Explain Trade-offs

For every solution, mention:

  • Pros: When it works best

  • Cons: Limitations

  • Alternatives: Other approaches

Real-world Context

Connect concepts to actual use cases:

  • "We'd use async for an API handling thousands of concurrent requests"

  • "Indexes are crucial here because this table has millions of rows"

Problem-solving Approach

  1. Clarify requirements

  2. Discuss trade-offs

  3. Propose solution

  4. Optimize if needed

  5. Consider edge cases

System Design Thinking

For senior roles, show you understand:

  • Scalability

  • Performance bottlenecks

  • Database design

  • API architecture

  • Caching strategies

  • Security implications


๐Ÿš€ Final Preparation Checklist

  • [ ] Practice coding common patterns (decorators, async, ORM queries)

  • [ ] Review your past projects - be ready to explain architecture decisions

  • [ ] Prepare questions about their tech stack and challenges

  • [ ] Understand the company's product and technical requirements

  • [ ] Practice explaining concepts in simple terms (rubber duck method)

  • [ ] Review recent Python/framework updates

  • [ ] Be ready to whiteboard database schema designs

  • [ ] Practice SQL queries and optimization scenarios

Remember: Senior developers are evaluated on:

  • Problem-solving ability

  • Code quality and best practices

  • System design thinking

  • Communication skills

  • Mentorship potential

Good luck with your interview! ๐ŸŽฏ