Preparing an interview with candidates can be a daunting task, and one for Python developers is no exception. But coming up with Python interview questions doesn’t have to be a headache.
What are some good Python interview questions? In this article, we’ll give you some examples of the type of knowledge you can test your candidates on in the form of some of the top Python interview questions for beginner, intermediate, and expert developers that you can use as a base when testing out your candidates.
Let’s get started!
Beginner Questions
When conducting a coding interview for python developers, you’ll want a candidate with strong foundations. Here are some basic Python interview questions:
1. What are Python’s key features?
- Interpreted language: Unlike compiled languages like C++ or C#, Python code does not need to be compiled before running. Instead, it uses an interpreter to run through the lines of code and executes them as they appear. This also has the side benefit of making Python cross-platform: so long as the platform has a functioning interpreter, Python code can run on it.
- Dynamically typed: Python does have the concept of variable types, but you can change a variable’s contents and type as much as needed as the program runs its course.
- Prepared for Object-Oriented Programming: Python has the concept of classes and objects and can be used to create object-oriented projects. It also allows for multiple inheritances (inheriting from more than one class at once).
2. How does python structure its code?
While other languages use code blocks and semicolons, Python relies solely on indentation to define scope.
3. What is the difference between arrays and lists?
Arrays and lists store data in the same way, but while lists can hold mixed data types, arrays can only hold one type of data, and that data type must be a basic type declared when it is initialized (strings are not allowed, for example, since they are a complex type).
Besides that, arrays need a module import to function, while lists do not. However, lists are more memory heavy than arrays when it comes to adding new elements.
This is valid Python code:
import array
int_array = array.array('i', [5, 10, 15, 20])
float_array = array.array('f',[1.0, 1.25, 1.50, 1.75, 2.0])
string_list = ['a string', 'another string', 'yet another string']
assorted_list = [5, 1.0, 'a string']
While this is not:
import array
assorted_array = array.array('i', [5, 1.0, 'a string'])
TypeError: integer argument expected, got float
4. What is init?
__init__ is a method that initializes an object’s state, akin to constructors in other languages. All classes feature an __init__ method (even if undefined), which allocates memory when a new class instance is created.
class Animal:
def __init__(self, name):
self.name = name
an_animal = Animal("Lassie");
another_animal = Animal("Rex");
print(an_animal.name)
print(another_animal.name);
Lassie
Rex
5. What are global and local variables?
Global variables are declared outside functions and methods and can be used anywhere at that level or below.
Local variables are declared inside functions and can only be used inside them. When the function ends, so does the ability to access that variable.
a_global_var = 5
def a_func():
a_local_var = 10
another_local_var = a_global_var + a_local_var
return another_local_var
a_func()
15
Trying to access a_local_var after the function’s definition would result in an error:
NameError: name 'a_local_var' is not defined
Intermediate Questions
This is where things start to get a bit harder. Here are some technical interview questions on Python:
1. What are the types of inheritance allowed in Python?
- Single Inheritance: A class inherits from a superclass.
- Multiple Inheritance: A class inherits from 2 or more superclasses.
- Multilevel Inheritance: A class inherits from a superclass and is then inherited by a derived class, forming a parent, child, and grandchild structure.
- Hierarchical Inheritance: A superclass is inherited by 2 or more derived classes.
2. What is a Python package?
Python allows developers to create packages: collections of related code that can then be imported and reused in various projects. These packages are akin to libraries in other programming languages.
3. How does Python memory management work?
Python offers an automatic method for memory management in the form of garbage collection. The memory manager keeps track of the references to an object and releases it from memory when that count reaches zero.
This is not a foolproof system, and there are specific cases where this automation doesn’t work as expected. Developers can interact with the garbage collector directly to remedy those more niche cases.
4. What is the difference between del, remove and pop?
Although they all serve a similar purpose, the first and by far biggest difference is that del is a keyword, while remove() and pop() are built-in methods for lists. The other major difference is in how they are used.
del can delete a value on the list (based on index), or the list itself.
# Deletes a value by index
del list_var[index]
# Deletes the list
del list_var
remove() takes a value and removes the first occurrence of that value on the list.
# Deletes by value (only the first occurrence)
list_var.remove(value)
pop() functions almost like delete does, in that it deletes a value by index. However, pop() also returns the deleted value.
# Deletes a value by index
deleted_value = list_var.pop(index)
Another curiosity that is worth mentioning is that both del and pop() accept negative indexes. In this case, it will delete starting from the end of the list, instead of at the start (with -1 being the last element, -2 the penultimate element, and so on).
a_list = [1, 2, 3, 4, 5]
another_list = [1, 2, 3, 4, 5]
a_list.pop(1)
print(a_list)
another_list.pop(-1)
print(another_list)
[1, 3, 4, 5]
[1, 2, 3, 4]
5. What is the function of self?
self is used in methods within a class to denote the current instance of the class running that method. Developers can use self to gain access to that particular instance’s internal state.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def print_greeting(self):
print("Hi, my name is", self.name, "and I'm", self.age, "years old!")
evelyn = Person("Evelyn", 23)
richard = Person("Richard", 24)
evelyn.print_greeting()
richard.print_greeting()
Hi, my name is Evelyn and I'm 23 years old!
Hi, my name is Richard and I'm 24 years old!
Expert Questions
This is where things get highly technical. If you want someone highly proficient in the language, it’s important to ask highly specialized Python tech interview questions. Let’s have a look at some examples:
1. What is the use of the single underscore _ variable in Python?
The single underscore is commonly used when you do not want to declare a variable for a return value, and would rather ignore it instead. The value is thrown away and cannot be used later.
for _ in range(10)
print ("Iterating...")
2. What is the difference between a deep and a shallow copy?
Both of these operations are supported by Python’s copy module, but the way they function is very different.
A shallow copy will usually create a new object with the same characteristics as the original, and will try to place its contents into the new object.
A deep copy will create a new object with the same characteristics as the original, but will try to place a new copy of its contents into the new object.
Let’s see an example:
import copy
a_list = [1, 2, 3, 4, 5, ["a", "b", "c", "d", "e"]]
shallow_copy = copy.copy(a_list)
deep_copy = copy.deepcopy(a_list)
# Initial State
print("--- Initial State ---")
print("Original:", a_list)
print("Shallow Copy:", shallow_copy)
print("Deep Copy:", deep_copy)
So far, so good. This is exactly as we expected. The copy was made and the objects are equal in their contents. Let’s modify our original and see what happens:
# Append operation on the main list
a_list.append(7)
print("--- Append: Main List ---")
print(" Original:", a_list)
print("Shallow Copy:", shallow_copy)
print(" Deep Copy:", deep_copy)
# Delete operation on the main list
del a_list[1]
print("--- Delete: Main List ---")
print(" Original:", a_list)
print("Shallow Copy:", shallow_copy)
print(" Deep Copy:", deep_copy)
--- Append: Main List ---
Original: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e'], 7]
Shallow Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
Deep Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
--- Delete: Main List ---
Original: [1, 3, 4, 5, ['a', 'b', 'c', 'd', 'e'], 7]
Shallow Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
Deep Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
Again, it’s working as intended. We’re modifying the original and both copies stay faithful to the original before any modifications. However, things change when we try to modify objects contained within the main list.
# Append operation on the inner list
a_list[5].append("f")
print("--- Append: Inner List ---")
print(" Original:", a_list)
print("Shallow Copy:", shallow_copy)
print(" Deep Copy:", deep_copy)
# Delete operation on the inner list
del a_list[5][1]
print("--- Delete: Inner List ---")
print(" Original:", a_list)
print("Shallow Copy:", shallow_copy)
print(" Deep Copy:", deep_copy)
--- Append: Inner List ---
Original: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e', 'f']]
Shallow Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e', 'f']]
Deep Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
--- Delete: Inner List ---
Original: [1, 2, 3, 4, 5, ['a', 'c', 'd', 'e', 'f']]
Shallow Copy: [1, 2, 3, 4, 5, ['a', 'c', 'd', 'e', 'f']]
Deep Copy: [1, 2, 3, 4, 5, ['a', 'b', 'c', 'd', 'e']]
Noticed what happened? When we change the inner list, the behavior of both copies changes drastically. The shallow copy mimics the changes made to the original’s internal list, while the deep copy remains unchanged.
This is because the shallow copy copies all objects from the original as they are. This means it copies the memory address of the inner list and adds that to the shallow copy. Changes made to this list are thus propagated since they are both pointing to the same memory address. The deep copy, on the other hand, can detect these types of issues, and makes a copy of the inner list and adds that copy’s address to the deep copy instead.
3. Why are Python’s private methods not actually private?
Python does not have a mechanism for encapsulation. However, it supports a limited system to allow developers to emulate private variables and methods in the form of name mangling. Although this hides the methods and variables, it does not make them inaccessible. A developer with knowledge of that class’ inner workings can still access it.
class A:
def say_hello():
print ("Hello!")
def __private():
print("I'm undercover A!")
class B(A):
def greet():
A.say_hello()
A._A__private()
print("And I'm B!")
print ("--- Let's introduce A! ---")
A.say_hello()
A._A__private()
print ("--- Let's introduce B! ---")
B.greet()
--- Let's introduce A! ---
Hello!
I'm undercover A!
--- Let's introduce B! ---
Hello!
I'm undercover A!
And I'm B!
As can be seen, even though this name mangling does its best to prevent developers misusing parts of other developers’ code, a savvy developer will be able to still access it directly.
4. What’s the difference between a Python module and a Python package?
The difference is not immediately evident, but modules and packages are distinct entities.
Modules are Python files that contain any amount of Python code.
Packages are collections, or a directory, of modules with an additional __init__.py file that defines the functions that are visible to third party code. Packages can contain other packages, provided each sub-package has its own __init__.py file.
When packages are imported, they are internally categorized as modules, and only the variables, functions, and classes defined in the __init__.py file will be visible. In contrast, all modules and sub-packages contained within will not be visible.
5. What is Monkey Patching? How to use it in Python?
Monkey patching is the practice of modifying the behavior of code at run-time. This is possible to do in Python by reassigning function addresses.
class A:
def greeting():
print ("Greetings!")
def monkey_greeting(self):
print ("Monkey Greetings!")
A.greeting = monkey_greeting
a = A()
a.greeting()
Monkey Greetings!
This can also be done across modules:
# original.py
class A:
def greeting():
print ("Greetings!")
# monkey.py
import original
def monkey_greeting(self):
print ("Monkey Greetings!")
original.A.greeting = monkey_greeting
orig = original.A()
orig.greeting()
Monkey Greetings!
Code Challenges and Other Exercises
If all else fails, or you want to test your candidates with more concrete Python interview exercises, you can assign them problems either engineered or inspired by similar ones encountered by your dev team.
We’ve got a whole article dedicated to code interview challenges, so be sure to check it out! It even includes some Python prompts you can use as a base to start from!
Conclusion
When it comes to Python programming interview questions, you’ll want to keep your questions as close to your company’s needs as much as possible.
If you deal with certain problems daily, or there are certain catches in Python that your team is constantly struggling with, test your candidates on those issues, or at least a small part of them. These questions are examples of the ones you can ask to assess your candidate’s knowledge.
If you’re looking for expert Python developers or any other expert developers, DistantJob can help you find remote talent that is highly competitive in terms of salary and can easily fit into your company’s culture. Don’t hesitate to contact us!