Python is one of the most popular languages in 2026. It dominates AI/ML, web development, data science, and automation.

This guide covers the 30 most common Python interview questions. Answers are short and practical with code examples.

Quick Reference — Python Key Features

FeatureDescription
Dynamically typedNo need to declare types (but type hints are recommended)
InterpretedRuns line by line, no compilation step
GILGlobal Interpreter Lock limits true multi-threading
Indentation-basedUses whitespace instead of braces for blocks
Duck typing“If it walks like a duck…” — type is determined by behavior
Garbage collectedAutomatic memory management with reference counting
Batteries includedLarge standard library for common tasks
Package managerpip (or uv for faster installs)

Beginner Questions (1-12)

1. What is Python?

Python is a high-level, interpreted, dynamically-typed programming language. It was created by Guido van Rossum and released in 1991. Python emphasizes readability with its use of indentation. It is widely used for web development, AI/ML, automation, and data analysis.

2. What is the difference between a list and a tuple?

A list is mutable — you can add, remove, and change elements. A tuple is immutable — once created, it cannot be changed. Tuples are faster and use less memory. Use tuples for fixed data and lists for data that needs to change.

my_list = [1, 2, 3]
my_list[0] = 10        # OK — lists are mutable

my_tuple = (1, 2, 3)
# my_tuple[0] = 10     # ERROR — tuples are immutable

3. What are Python’s data types?

Python has these built-in data types: int, float, str, bool, list, tuple, dict, set, frozenset, bytes, and NoneType. Everything in Python is an object, including numbers and strings.

4. What is the difference between == and is?

== checks if two values are equal. is checks if two variables point to the same object in memory. Use == for value comparison and is for identity comparison (usually only with None).

a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True — same values
print(a is b)   # False — different objects

x = None
print(x is None)  # True — correct way to check None

5. What are *args and **kwargs?

*args lets a function accept any number of positional arguments as a tuple. **kwargs lets it accept any number of keyword arguments as a dictionary. They make functions flexible without specifying all parameters upfront.

def greet(*args, **kwargs):
    for name in args:
        greeting = kwargs.get("greeting", "Hello")
        print(f"{greeting}, {name}!")

greet("Alex", "Sam", greeting="Hi")
# Hi, Alex!
# Hi, Sam!

6. What is a dictionary?

A dictionary stores key-value pairs. Keys must be hashable (immutable). Lookups by key are O(1) on average. Since Python 3.7, dictionaries maintain insertion order.

user = {"name": "Alex", "age": 25}
print(user["name"])       # Alex
user["email"] = "alex@example.com"  # add new key

7. What is list comprehension?

List comprehension is a concise way to create lists. It combines a for loop and an optional condition in one line. It is faster than a regular for loop because it is optimized internally.

# Regular loop
squares = []
for x in range(10):
    squares.append(x ** 2)

# List comprehension
squares = [x ** 2 for x in range(10)]

# With condition
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]

8. What is the difference between a shallow copy and a deep copy?

A shallow copy creates a new object but references the same nested objects. A deep copy creates a new object and recursively copies all nested objects. Use copy.deepcopy() when you have nested mutable objects.

import copy

original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0][0] = 99
print(shallow[0][0])  # 99 — shared reference
print(deep[0][0])     # 1  — independent copy

9. What are f-strings?

F-strings (formatted string literals) embed expressions inside strings using f"" and curly braces. They were introduced in Python 3.6 and are the fastest and most readable way to format strings.

name = "Alex"
age = 25
print(f"My name is {name} and I am {age} years old.")
print(f"Next year I will be {age + 1}.")

10. What is None in Python?

None is Python’s null value. It represents the absence of a value. It is a singleton — there is only one None object. Functions that do not explicitly return a value return None. Always check with is None, not == None.

11. What is the difference between append() and extend()?

append() adds one element to the end of a list. extend() adds all elements from an iterable to the list. With append(), if you pass a list, it becomes a nested list.

a = [1, 2]
a.append([3, 4])   # [1, 2, [3, 4]]

b = [1, 2]
b.extend([3, 4])   # [1, 2, 3, 4]

12. What is PEP 8?

PEP 8 is Python’s official style guide. It covers naming conventions, indentation (4 spaces), line length (79 characters), and code organization. Following PEP 8 makes code readable and consistent. Use tools like ruff or black to auto-format.


Intermediate Questions (13-22)

13. What are decorators?

Decorators wrap a function to modify its behavior without changing its code. They use the @ syntax. Common uses include logging, caching, authentication, and timing. A decorator is a function that takes a function and returns a new function.

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

@timer
def slow_function():
    import time
    time.sleep(1)

14. What are generators?

Generators produce values lazily using yield. They do not store all values in memory. This makes them efficient for large datasets. A generator function returns a generator object that you can iterate over.

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(10):
    print(num)  # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

15. What is the GIL (Global Interpreter Lock)?

The GIL is a mutex in CPython that allows only one thread to execute Python code at a time. This means multi-threaded Python code does not achieve true parallelism for CPU-bound tasks. Use multiprocessing for CPU-bound work and threading for I/O-bound work. Python 3.13 introduced an experimental free-threaded mode without GIL.

16. What is the difference between @staticmethod and @classmethod?

@staticmethod is a regular function inside a class — it does not access the class or instance. @classmethod receives the class as its first argument (cls). Use @classmethod for factory methods and @staticmethod for utility functions.

class User:
    def __init__(self, name: str):
        self.name = name

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(data["name"])

    @staticmethod
    def validate_name(name: str) -> bool:
        return len(name) > 0

17. What are context managers?

Context managers handle setup and cleanup using with statements. They guarantee cleanup even if an exception occurs. The most common use is file handling. You can create custom context managers with __enter__ and __exit__ methods or using @contextmanager.

# Built-in file context manager
with open("data.txt", "r") as f:
    content = f.read()
# File is automatically closed here

# Custom context manager
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    yield
    print(f"Elapsed: {time.time() - start:.2f}s")

18. What are type hints?

Type hints annotate function parameters and return types. They do not enforce types at runtime — Python is still dynamically typed. But they help IDEs, linters, and tools like mypy catch bugs before running the code. Type hints are recommended in all production Python code.

def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

from typing import Optional
def find_user(user_id: int) -> Optional[dict]:
    return None  # or a user dict

19. What is the difference between a module and a package?

A module is a single .py file. A package is a directory containing modules and an __init__.py file. Packages organize related modules. You import them with import package.module or from package import module.

20. What are dunder (magic) methods?

Dunder methods (double underscore) let you define how objects behave with built-in operations. __init__ handles initialization, __str__ handles string conversion, __len__ handles len(), and __eq__ handles equality comparison.

class Vector:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __add__(self, other: "Vector") -> "Vector":
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"

v = Vector(1, 2) + Vector(3, 4)  # Vector(4, 6)

21. What are dataclasses?

Dataclasses automatically generate __init__, __repr__, __eq__, and other methods. They reduce boilerplate for classes that mainly store data. Use @dataclass decorator. For immutable data, set frozen=True.

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    email: str = ""

user = User("Alex", 25)
print(user)  # User(name='Alex', age=25, email='')

22. What is the difference between multi-threading and multi-processing?

Multi-threading runs threads in the same process, sharing memory. Due to the GIL, only one thread runs Python code at a time — good for I/O-bound tasks. Multi-processing runs separate processes with their own memory and GIL — good for CPU-bound tasks.

# Threading — good for I/O
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=5) as executor:
    results = executor.map(fetch_url, urls)

# Multiprocessing — good for CPU
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as executor:
    results = executor.map(heavy_computation, data)

Advanced Questions (23-30)

23. What is async/await in Python?

async defines a coroutine function. await pauses execution until the awaited coroutine finishes. This is Python’s way of handling concurrent I/O operations without threads. Use asyncio or frameworks like FastAPI that are built on async.

import asyncio

async def fetch_data(url: str) -> str:
    await asyncio.sleep(1)  # simulates network request
    return f"Data from {url}"

async def main():
    results = await asyncio.gather(
        fetch_data("https://api1.example.com"),
        fetch_data("https://api2.example.com"),
    )
    print(results)

asyncio.run(main())

24. What are metaclasses?

A metaclass is the class of a class. It controls how classes are created. The default metaclass is type. Custom metaclasses let you modify class creation — for example, to register all subclasses, validate class attributes, or enforce coding standards.

class SingletonMeta(type):
    _instances: dict = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

a = Database()
b = Database()
print(a is b)  # True — same instance

25. What are descriptors?

Descriptors control attribute access using __get__, __set__, and __delete__ methods. They are the mechanism behind @property, @classmethod, and @staticmethod. Use them for validation, lazy computation, or custom attribute behavior.

class Positive:
    def __set_name__(self, owner, name):
        self.name = name

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError(f"{self.name} must be positive")
        obj.__dict__[self.name] = value

    def __get__(self, obj, objtype=None):
        return obj.__dict__.get(self.name, 0)

class Product:
    price = Positive()
    quantity = Positive()

26. What is the difference between new and init?

__new__ creates the object instance. __init__ initializes it after creation. __new__ is called first and must return the instance. Override __new__ for singletons, immutable types, or custom object creation. In most cases, you only need __init__.

27. How does Python manage memory?

Python uses reference counting as its primary mechanism. When an object’s reference count drops to zero, it is freed immediately. A cyclic garbage collector handles reference cycles. The gc module lets you inspect and control garbage collection. Use __slots__ to reduce memory usage in classes with many instances.

class Point:
    __slots__ = ("x", "y")  # saves ~40% memory vs regular class

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

28. What are Protocol classes (structural typing)?

Protocols define interfaces based on structure, not inheritance. A class matches a Protocol if it has the required methods — no explicit inheritance needed. This is Python’s version of duck typing with type checker support. Added in Python 3.8.

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

class Circle:
    def draw(self) -> None:
        print("Drawing circle")

def render(shape: Drawable) -> None:
    shape.draw()

render(Circle())  # Works — Circle has draw() method

29. What is pattern matching?

Pattern matching (match/case) was added in Python 3.10. It is more powerful than if/elif chains. It supports value matching, structure matching, guard conditions, and capture variables. Use it for complex data processing and state machines.

def handle_command(command: dict) -> str:
    match command:
        case {"action": "greet", "name": str(name)}:
            return f"Hello, {name}!"
        case {"action": "add", "x": int(x), "y": int(y)}:
            return f"Result: {x + y}"
        case _:
            return "Unknown command"

30. How do you optimize Python performance?

Key strategies: use built-in functions and data structures (they are C-optimized). Use list comprehensions over loops. Use generators for large datasets. Profile with cProfile before optimizing. Use functools.lru_cache for memoization. For CPU-intensive work, consider Cython, Numba, or Rust extensions.

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # instant — cached results

Bonus Tips for Interviews

  1. Practice coding problems — Use platforms like LeetCode or HackerRank. Focus on arrays, strings, dictionaries, and recursion. Python’s clean syntax makes it ideal for whiteboard coding.

  2. Know the standard library — Interviewers expect you to know collections, itertools, functools, pathlib, and typing. These solve common problems without third-party packages.

  3. Understand the GIL — This is one of the most asked advanced questions. Know when to use threading vs multiprocessing vs async. Python 3.13’s free-threaded mode is a hot topic in 2026.

  4. Type hints matter — Production Python code in 2026 uses type hints everywhere. Show that you write type-annotated code by default. Check our Python type hints tutorial for details.

  5. Know a web framework — FastAPI is the most popular choice in 2026 for new projects. Know how to build a simple API endpoint with path parameters, query parameters, and request bodies. See our FastAPI tutorial.

  6. Show testing skills — Write tests with pytest. Know about fixtures, parametrize, and mocking. Testing questions are common in senior interviews.

  7. Read PEP 8 and use modern tooling — Mention ruff for linting and uv for package management. These are the modern standards.