---
title: "Python Interview Questions"
description: "If you write code in Python and love the language, this post is for you. Let’s do a little interview and assess how well you know this language."
canonical_url: "https://otabek.io/blogs/python-interview-questions"
md_url: "https://otabek.io/blogs/python-interview-questions.md"
language: "en"
last_updated: "2024-07-20"
tags: ["Python"]
---

# Python Interview Questions

If you write code in Python and enjoy working with this language, then this post is for you. Let’s have an interview and assess how well you understand Python.

![Interview meme](https://i0.wp.com/collegecore.com/wp-content/uploads/2018/02/job-interview-meme-17.jpg?ssl=1)

### Question 1

**Question**:  
Explain why the following code behaves the way it does:

```python-run
a = 256
b = 256
print(a is b) # True

x = 257
y = 257
print(x is y) # False
```

**Answer**:  
Python caches numbers in the range of **-5 to 256** [source](https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong).  
You can learn more about memory management in Python by reading the [Python Memory Management](/blogs/python-memory-management) post.

### Question 2

**Question**:  
How does `import` work in Python? Why does the following code return the results it does?

```python-run
import math
print(math.pi) # 3.141592653589793

math.pi = 3
print(math.pi) # 3

import math
print(math.pi)
```

**Answer**:  
Python **frozen modules** ensure that the Python interpreter loads a module into the [sys.modules](https://docs.python.org/3/library/sys.html#sys.modules) dictionary only once and reuses it afterward. To see this in action, open a Python3 interactive shell (open a terminal, type `python3`, and hit enter), and try importing the `__hello__` module:

```python-run
import __hello__
# This should print "Hello world!"

import __hello__
# This will not print anything, beacuase it will only import once
```

### Question 3

**Question**:  
What is `__pycache__`, and what are `.pyc` files?

**Answer**:  
Whenever you run a Python program, the interpreter compiles your code to **bytecode** (for efficiency) and stores it in a folder named **`__pycache__`** [[source]](https://docs.python.org/3/glossary.html#term-interpreted).

If you look inside this folder, you’ll see `.pyc` or `.pyo` files. Rather than reading and executing the code line by line, the Python interpreter executes the cached `.pyc` bytecode in the [virtual machine](https://docs.python.org/3/glossary.html#term-virtual-machine). This process is similar to Java.

### Question 4

**Question**:  
What impact does exception handling have on the performance of a program? If it slows down the program, how would you minimize this slowdown?

**Answer**:  
Python uses a **stack** for exception handling. When an exception occurs, the interpreter looks for the exception handler in the **call stack** (your `try-except` code). If no handler is found, Python terminates the program and prints a **traceback** in the terminal.

Python encourages the use of [EAFP](https://docs.python.org/3.6/glossary.html#term-eafp) ("it's easier to ask for forgiveness than permission") over [LBYL](https://docs.python.org/3.6/glossary.html#term-lbyl) ("look before you leap") style coding. To write efficient and simple code, consider a scenario where users provide a list of numbers [1, 2, 3, 4], and the program calculates the sum. If you are confident that users will input numeric lists 90% of the time, you can use **try-except**. If not, it’s better to use an **if-else** statement to handle the control flow (based on personal experience and advice from Core Python Developers).

### Question 5

**Question**:  
Why does the following code return `False`? Both objects have the same value. How would you correct it?

```python-run
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

x = Node(1)
y = Node(1)

print(x == y) # False
```

**Answer**:  
There are two solutions for this.

**Solution 1**: Using [dunder methods](https://mathspp.com/blog/pydonts/dunder-methods). You can learn more by following the link.

```python-run
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

    # The equal dunder method is used for the == operator.
    # If self is Node(1) and other is also Node(1), it compares their val attributes.
    def __eq__(self, other):
        return self.val == other.val

x = Node(1)
y = Node(1)

print(x == y) # True
```

**Solution 2**: The [dataclasses](https://docs.python.org/3/library/dataclasses.html) module is useful here. If you don’t want to write all the dunder methods yourself, you can use the dataclass decorator, and it will automatically generate the necessary methods for you.

```python-run
from dataclasses import dataclass

@dataclass
class Node:
    val: any
    next: None = None  # Default is None, but it can be changed.

x = Node(1)
y = Node(1)

print(x == y) # True
```

### Question 6

**Question**:  
What is the difference between a **metaclass** and a **class**? Can you demonstrate creating a metaclass?

**Answer**:  
**Classes** create objects, and classes themselves are objects. **Metaclasses** create classes.

```python
MyClass = MetaClass()  # Class created
my_object = MyClass()  # Object created from the class

# type() helps you create a metaclass:
MyClass = type('MyClass', (), {})

# type(name, base, dict)
# name => class name
# base => tuple of base classes
# dict => dictionary for attributes
MyMetaClass = type("MySimpleClass", (), {"val": 12})
obj = MyMetaClass()
print(obj.val)  # 12
```

### Question 7

**Question**:  
We have three different types of lists:

- [list](https://docs.python.org/3/library/functions.html#func-list)
- [collections.UserList](https://docs.python.org/3/library/collections.html#collections.UserList)
- [np.array](https://www.w3schools.com/python/numpy/numpy_creating_arrays.asp) [[numpy docs](https://numpy.org/doc/stable/reference/generated/numpy.array.html)].

If we create a list with `range(1, 10_000_000)` and check the sum using `sum()`, we find that the regular `list()` computes the sum much faster than the others. Why is it so much faster, and how is it computed so efficiently?

```python
import time
import numpy as np
from collections import UserList

regular_list = list(range(1, 10000000))  # Very fast
np_list = np.array(range(1, 10000000))  # Slow
user_list = UserList(range(1, 10000000))  # Slowest

start = time.perf_counter()
print("Sum of regular list:", sum(regular_list), f"Time taken: {time.perf_counter() - start:.6f} seconds")

start = time.perf_counter()
print("Sum of NumPy array:", sum(np_list), f"Time taken: {time.perf_counter() - start:.6f} seconds")

start = time.perf_counter()  # np.sum() is another matter
print("np.sum of NumPy array:", np.sum(np_list), f"Time taken: {time.perf_counter() - start:.6f} seconds")

start = time.perf_counter()
print("Sum of UserList:", sum(user_list), f"Time taken: {time.perf_counter() - start:.6f} seconds")
```

**Answer**:  
This question is for you to figure out. Send your answer to me via email at <a href="mailto:ask@otabek.io">ask@otabek.io</a>.

To make the most of this post, study it and teach your friends (share it)!

---

```quiz
{
  "quiz": {
    "id": "python-interview-quiz",
    "title": "Python Interview Quiz",
    "description": "Test your understanding of Python internals",
    "questions": [
      {
        "id": "q1",
        "type": "single-choice",
        "question": "Why does `256 is 256` return True but `257 is 257` might return False?",
        "options": [
          { "id": "a", "text": "Python caches integers from -5 to 256", "description": "" },
          { "id": "b", "text": "257 is too large for Python to handle", "description": "Python can handle integers of any size. The difference is about object caching, not size limits." },
          { "id": "c", "text": "It's a bug in Python", "description": "This is intentional behavior for memory optimization, not a bug." },
          { "id": "d", "text": "The `is` operator doesn't work with large numbers", "description": "The `is` operator works fine - it compares object identity. The difference is whether Python reuses the same object." }
        ]
      },
      {
        "id": "q2",
        "type": "drag-fill",
        "question": "Complete the code to make two Node objects with the same value equal:",
        "template": "class Node:\n    def __init__(self, {{b1}}):\n        self.val = {{b2}}\n\n    def {{b3}}(self, other):\n        return self.{{b4}} == other.val",
        "options": [
          { "id": "opt1", "content": "val" },
          { "id": "opt2", "content": "val" },
          { "id": "opt3", "content": "__eq__" },
          { "id": "opt4", "content": "val" }
        ],
        "blanks": [
          { "id": "b1" },
          { "id": "b2" },
          { "id": "b3" },
          { "id": "b4" }
        ]
      },
      {
        "id": "q3",
        "type": "single-choice",
        "question": "What does Python store in the `__pycache__` folder?",
        "options": [
          { "id": "a", "text": "Compiled bytecode (.pyc files)", "description": "" },
          { "id": "b", "text": "Temporary variables", "description": "Variables are stored in memory during runtime, not in __pycache__." },
          { "id": "c", "text": "Import history", "description": "sys.modules tracks imports, not __pycache__." },
          { "id": "d", "text": "Error logs", "description": "__pycache__ contains compiled code, not logs." }
        ]
      },
      {
        "id": "q4",
        "type": "single-choice",
        "question": "When you import a module twice, Python will:",
        "options": [
          { "id": "a", "text": "Load it fresh each time", "description": "Python caches modules in sys.modules and reuses them." },
          { "id": "b", "text": "Reuse the cached module from sys.modules", "description": "" },
          { "id": "c", "text": "Throw an ImportError", "description": "Importing twice is perfectly valid and won't cause an error." },
          { "id": "d", "text": "Create a copy of the module", "description": "Python reuses the same module object, it doesn't create copies." }
        ]
      },
      {
        "id": "q5",
        "type": "drag-drop",
        "question": "Arrange the steps of how Python handles a raised exception:",
        "hint": "Think about what happens when an error occurs",
        "items": [
          { "id": "step1", "content": "Exception is raised" },
          { "id": "step2", "content": "Python looks for handler in call stack" },
          { "id": "step3", "content": "Handler found: execute except block" },
          { "id": "step4", "content": "No handler: print traceback and exit" }
        ]
      }
    ]
  },
  "answers": {
    "q1": { "correctOptionIds": ["a"] },
    "q2": { "correctPlacements": { "b1": "opt1", "b2": "opt2", "b3": "opt3", "b4": "opt4" } },
    "q3": { "correctOptionIds": ["a"] },
    "q4": { "correctOptionIds": ["b"] },
    "q5": { "correctOrder": ["step1", "step2", "step3", "step4"] }
  }
}
```


## Sitemap

See the full [Markdown sitemap](/sitemap.md) for all pages.
