Essential Python Interview Questions and Technical Preparation Guide

In Django, middleware compoennts process requests and responses through various methods:

  • process_request: Executes when a request comes in, handling authentication
  • process_view: Runs after URL routing matches a view function
  • process_exception: Triggered when an exception occurs
  • process_template_response: Executes during template rendering
  • process_response: Runs when a response is returned

FBV vs CBV in Django

FBV (Function-Based Views) and CBV (Class-Based Views) are fundamentally similar approaches to handling requests in Django. CBV offers several advantages:

  • Improved code reusability through object-oriented programming
  • Support for Mixins and multiple inheritance
  • Ability to handle different HTTP methods with separate functions instead of conditional statements
  • Better code organization and readability

Django Request Object Creation

The Django request object is created in the WSGI handler:

class WSGIHandler(base.BaseHandler):
    request = self.request_class(environ)

When a request reaches the WSGIHandler class, it executes the method and encapsulates the environ parameter into a request object.

Adding Decorators to CBV

To add decorators to Class-Based Views in Django:

from django.utils.decorators import method_decorator

@method_decorator(authenticate_user)
def post(self, request):
    # Handle POST request
    pass

Django ORM Methods

Common Django ORM methods include:

all(): Returns all objects in the queryset
filter(**kwargs): Returns objects matching the given criteria
get(**kwargs): Returns a single object matching the criteria; raises MultipleObjectsReturned or DoesNotExist if multiple or no objects found
exclude(**kwargs): Returns objects that don't match the given criteria
order_by(*field): Sorts the queryset by specified fields
count(): Returns the number of objects in the queryset
first(): Returns the first object in the queryset
exists(): Returns True if the queryset contains any objects

select_related vs prefetch_related

When dealing with foreign key relationships:

  • select_related performs a SQL join to fetch related objects in a single query
  • prefetch_related executes separate queries for each table and then combines the results in Python

Use select_related for foreign key and one-to-one relationships, and prefetch_related for many-to-many and reverse foreign key relationships.

Django CSRF Implementation

Django's CSRF protection works as follows:

  1. When responding to a client's first request, Django generates a random token, stores it in the session, and sends it to the client in a cookie
  2. For subsequent requests (like form submissions), the client includes this token in the request data or headers
  3. The server validates that the token from the request matches the one stored in the session

Configuring Redis Cache in Django

Yes, Django can use Redis for caching. Here's how to configure it:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
            # "PASSWORD": "your_password",
        }
    }
}

Purpose of Name in Django URL Routing

The name parameter in Django URL patterns allows you to reference URLs by name rather than hardcoding paths. This provides flexibility to change URL patterns without updating templates or views that reference them.

Django REST Framework Components

Key components in Django REST Framework include:

  • Authentication
  • Permissions (Authorization)
  • Throttling (Rate limiting)
  • Versioning
  • Parsers
  • Serializers
  • Pagination
  • Routers
  • Views
  • Renderers

Django REST Framework Authentication Flow

The authentication process in DRF follows these steps:

  1. When a user attempts to log in, the login class's as_view() method is called, entering the APIView class's dispatch method
  2. The initialize_request method executes, encapsulating the request and authentication objects
  3. The initial method calls perform_authentication, wich runs the user method
  4. The user method then executes _authenticate(), which handles the actual authentication

Handling Large Files with Limited Memory

Question: How would you process a 10GB file with only 4GB of RAM, modifying only the get_lines function?

from mmap import mmap

def get_lines(file_path):
    with open(file_path, "r+") as file:
        memory_map = mmap(file.fileno(), 0)
        start_position = 0
        for index, character in enumerate(memory_map):
            if character == b"\n":
                yield memory_map[start_position:index+1].decode()
                start_position = index+1

if __name__ == "__main__":
    for line in get_lines("large_file.txt"):
        print(line)

Key considerations: The file is too large to fit in memory, so we need to process it in chunks. We must track our position between reads and balance chunk size to avoid excessive I/O operations.

Directory Traversal Function

Complete the function to print all file paths in a directory and its subdirectories:

import os

def print_directory_contents(path):
    """
    This function takes a directory path as input
    and prints the paths of all files in the directory
    and its subdirectories.
    """
    for child in os.listdir(path):
        child_path = os.path.join(path, child)
        if os.path.isdir(child_path):
            print_directory_contents(child_path)
        else:
            print(child_path)

Day of Year Calculation

Write a function to determine the day of the year for a given date:

import datetime

def day_of_year():
    year = int(input("Enter year: "))
    month = int(input("Enter month: "))
    day = int(input("Enter day: "))
    
    target_date = datetime.date(year=year, month=month, day=day)
    year_start = datetime.date(year=year, month=1, day=1)
    
    return (target_date - year_start).days + 1

Dictionary Sorting by Value

Sort the following dictionary by its values: d = {'a': 24, 'g': 52, 'i': 12, 'k': 33}

sorted(d.items(), key=lambda item: item[1])

String to Dictionary Conversion

Convert the string "k:1|k1:2|k2:3|k3:4" to a dictionary:

input_string = "k:1|k1:2|k2:3|k3:4"

def string_to_dict(s):
    result = {}
    for item in s.split('|'):
        key, value = item.split(':')
        result[key] = int(value)
    return result

# Using dictionary comprehension
result_dict = {k:int(v) for item in input_string.split("|") for k, v in (item.split(":"), )}

Python Built-in Data Structures

Python provides several built-in data structures:

  • Integer (int)
  • Floating-point (float)
  • Complex numbers (complex)
  • String (str)
  • List (list)
  • Tuple (tuple)
  • Dictionary (dict)
  • Set (set)

Note: In Python 3, there's no separate long type; int has unlimited precision.

Singleton Pattern Implementations

Two ways to implement the singleton pattern in Python:

Using a decorator:

def singleton(cls):
    instances = {}
    
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class DatabaseConnection:
    pass

db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True

Using a base class:

class Singleton:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance
        
class ConfigurationManager(Singleton):
    pass

config1 = ConfigurationManager()
config2 = ConfigurationManager()
print(config1 is config2)  # True

Sum of Numbers 1-100 in One Line

sum(range(1, 101))

is vs == in Python

is compares object identity (whether two variables point to the same object in memory), while == compares object equality (whether two objects have the same value). By default, == calls the __eq__ method of the object.

Finding Most Frequent Words in a Text

import re
from collections import Counter

def find_most_frequent_words(file_path, count=10):
    with open(file_path) as file:
        # Normalize text: replace non-alphanumeric characters with spaces
        normalized_text = re.sub(r"\W+", " ", file.read())
        words = normalized_text.split()
        
        # Count word frequencies and return most common
        word_counts = Counter(words)
        return [word for word, _ in word_counts.most_common(count)]

Python Memory Management

Python uses three main mechanisms for memory management:

  1. Reference counting: Each object keeps track of how many references point to it. When the count reaches zero, the object is deallocated.
  2. Garbage collection: Handles circular references that reference counting can't resolve.
  3. Memory pools: Python manages memory in pools to reduce overhead from frequent allocation and deallocation.

Optimization techniques include manual garbage collection, adjusting garbage collection thresholds, and avoiding circular references.

Lambda Functions in Python

Lambda functions are anonymous functions defined with the lambda keyword. They can take any number of arguments but can only have one expression. Benefits include:

  • Concise syntax for simple functions
  • Useful for functional programming constructs like map, filter, and reduce
  • Handy as callback functions

Example:

multiply = lambda x, y: x * y
print(multiply(5, 3))  # Output: 15

Understanding Design Patterns

Design patterns are reusable solutions to common programming problems. They represent best practices evolved from experienced developers' collective wisdom. Common patterns include:

  • Factory Pattern
  • Singleton Pattern
  • Observer Pattern
  • Strategy Pattern
  • Decorator Pattern

Singleton Pattern Use Cases

The singleton pattern is useful in scenarios involving:

  • Resource sharing (e.g., database connections, thread pools)
  • Configuration management
  • Logging systems
  • Cache management
  • Device drivers (where only one instance should control a resource)

Performance Timer Decorator

import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Function {func.__name__} executed in {end_time - start_time:.6f} seconds")
        return result
    return wrapper

@measure_time
def process_data():
    # Simulate data processing
    time.sleep(1)
    return "Processing complete"

print(process_data())

Closures in Python

A closure occurs when a nested function references a value from its enclosing scope. The closure function remembers the values from the enclosing lexical scope even when the program flow is no longer in that scope.

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times_three = make_multiplier(3)
print(times_three(10))  # Output: 30

Generators vs Iterators

Iterators are objects that implement the iterator protocol (__iter__ and __next__ methods). They represent a stream of data and return one element at a time.

Generators are a special type of iterator created using functions with the yield keyword. They automatically implement the iterator protocol and maintain their state between calls.

Key differences:

  • Generators are simpler to write (using yield instead of implementing __iter__ and __next__)
  • Generators are more memory-efficient as they generate values on demand
  • Generators automatically raise StopIteration when exhausted

Grouping Numbers in Tuples

Create groups of three numbers from 1 to N:

N = 100
result = [[num for num in range(1, N+1)][i:i+3] for i in range(0, N, 3)]
print(result)

Yield Keyword in Python

The yield keyword turns a function into a generator. When a function contains yield, it becomes a generator function that returns a generator iterator. The generator maintains its state between calls, allowing it to produce a sequence of values over time rather than computing them all at once. This is memory-efficient for large sequences.

def fibonacci(n):
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

for num in fibonacci(10):
    print(num)

Understanding Processes, Threads, and Coroutines

Processes are independent execution units with their own memory space. They are the unit of resource allocation and provide true parallelism but have higher overhead for creation and communication.

Threads are lightweight execution units within a process. They share the same memory space, making communication easier but requiring synchronization. Threads provide concurrency within a single process.

Coroutines are even lighter-weight units of execution that are managed cooperatively by the program rather than the operating system. They are ideal for I/O-bound tasks and can handle thousands of concurrent operations with minimal overhead.

Python Async Use Cases

Asynchronous programming in Python is particularly useful for:

  • I/O-bound operations (network requests, database queries, file operations)
  • Applications that need to handle many concurrent connections
  • Real-time applications (chat servers, live updates)
  • Web scraping and crawling
  • Applications where responsiveness is critical

Thread Competition in Python

Thread competition occurs when multiple threads attempt to access shared resources simultaneously, potentially leading to race conditions. Since threads share the same memory space, concurrent access to shared data can result in inconsistent states if not properly synchronized.

Lock Types in Python

Python provides several synchronization primitives:

  • Lock: A basic mutual exclusion lock that can be acquired by only one thread at a time
  • RLock (Reentrant Lock): Allows a thread to acquire the same lock multiple times
  • Semaphore: Allows a fixed number of threads to access a resource
  • Event: Allows threads to wait for a specific event to occur
  • Condition: Combines a lock with a wait/notify mechanism

Understanding Deadlocks

A deadlock occurs when two or more threads are blocked forever, each waiting for a resource held by the other. The classic "dining philosophers" problem illustrates this scenario. Deadlocks happen when four conditions are met simultaneously:

  1. Mutual exclusion: Resources cannot be shared
  2. Hold and wait: Threads hold resources while waiting for others
  3. No preemption: Resources cannot be forcibly taken
  4. Circular wait: A circular chain of threads exists where each holds a resource needed by the next

Tags: Django python web development Interview Questions programming

Posted on Sat, 12 Sep 2026 16:43:18 +0000 by svenski