Python5 min read11 sections

Python interview questions for freshers: 10 common questions with runnable answers

10 questions that come up again and again in fresher Python interviews. Each has a plain-English answer, a short piece of code, its real output, and a note on what the interviewer is really checking.

Solve “First Occurrence Map”

Free. Runs in your browser, no install.

Updated Written for: Students and freshers preparing for their first Python or data engineering interview.

What you will learn

  • Lists and dictionaries can change, tuples cannot, and sets hold unique values only.
  • A mutable default argument is shared between calls, which is a classic bug.
  • == compares values and is compares identity. A shallow copy shares inner objects.
  • A generator produces values one at a time, so it saves memory on large data.
On this page
  1. 1. What is the difference between a list, a tuple, a set and a dictionary?
  2. 2. What is the difference between == and is?
  3. 3. What is the mutable default argument trap?
  4. 4. What is a list comprehension?
  5. 5. What are *args and **kwargs?
  6. 6. What is the difference between a shallow copy and a deep copy?
  7. 7. How do you count how often each item appears?
  8. 8. How do you remove duplicates and keep the original order?
  9. 9. What is a generator, and why use one?
  10. 10. How do you handle errors?
  11. How to prepare

Fresher interviews do not ask about advanced Python. They ask the same 10 or so questions, to see whether you understand the basics well enough to explain them. This guide answers them with code you can run. Every output below came from really running the code, so you can trust what each snippet prints.

What is the difference between a list, a tuple, a set and a dictionary?

All four hold several values. They differ in whether they can change, whether they keep order, and whether they allow repeats.

PythonPySpark
colors = ["red", "green", "red"]      # list: ordered, can change, repeats allowed
point = (10, 20)                      # tuple: ordered, cannot change
unique = {"red", "green", "red"}      # set: no repeats, no order to rely on
person = {"name": "Asha", "age": 25}  # dict: name -> value pairs

colors.append("blue")
print(colors)
print(len(unique))
print(person["name"])
['red', 'green', 'red', 'blue']
2
Asha
PythonPySpark
point[0] = 99
TypeError: 'tuple' object does not support item assignment

The list grew, the set kept only two colours, and the dictionary was read by key. The tuple refused to change, because tuples are immutable (cannot be modified). Use a list for a collection that grows, a tuple for a fixed pair such as coordinates, a set to remove duplicates or test membership, and a dictionary to look values up by name.

What is the difference between == and is?

== asks "are the values equal?" and is asks "is this the very same object in memory?"

PythonPySpark
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)
print(a is b)
print(a is c)
True
False
True

a and b hold equal values but are two separate lists, so == is True and is is False. c is another name for the same list as a, so is is True. Use == to compare values, and is only to compare with None, as in if x is None.

What is the mutable default argument trap?

A default value in a function definition is created once, when the function is defined, not each time it is called. If that default is a list, every call shares it.

PythonPySpark
def add_item(item, basket=[]):
    basket.append(item)
    return basket

print(add_item("pen"))
print(add_item("book"))
['pen']
['pen', 'book']

The second call returned ['pen', 'book'], although you might expect only ['book']. The same list was reused. The fix is to use None as the default and create a new list inside the function:

PythonPySpark
def add_item_safe(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

print(add_item_safe("pen"))
print(add_item_safe("book"))
['pen']
['book']

What is a list comprehension?

A short way to build a list from another list, with an optional filter.

PythonPySpark
amounts = [1200, 800, 450, 2500, 300]

big = []
for amount in amounts:
    if amount > 500:
        big.append(amount)

big_short = [amount for amount in amounts if amount > 500]
print(big)
print(big_short)
print(big == big_short)
[1200, 800, 2500]
[1200, 800, 2500]
True

Both ways produce the same list, and the comprehension fits on one line. Use it for short, simple cases, and a normal loop when the logic needs several steps.

What are *args and **kwargs?

They let a function accept any number of extra values. *args collects extra positional values into a tuple, and **kwargs collects extra named values into a dictionary.

PythonPySpark
def describe(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

describe(1, 2, city="Pune", amount=1200)
args: (1, 2)
kwargs: {'city': 'Pune', 'amount': 1200}

The names args and kwargs are only a convention. The stars are what matter. You will see them in libraries that pass options through to other functions.

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

A shallow copy makes a new outer list but shares the inner objects. A deep copy copies everything, all the way down.

Shallow copy
Inner lists are shared

The outer list is new.

Both lists point to the same inner lists.

Result: [[1, 2, 99], [3, 4]]

Deep copy
Everything is copied

Inner lists are copied too.

The original's change cannot reach it.

Result: [[1, 2], [3, 4]]

Figure 1. Both copies look the same at first. The difference shows when you change something inside.
PythonPySpark
import copy

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

original[0].append(99)

print("original:", original)
print("shallow: ", shallow)
print("deep:    ", deep)
original: [[1, 2, 99], [3, 4]]
shallow:  [[1, 2, 99], [3, 4]]
deep:     [[1, 2], [3, 4]]

Changing an inner list of the original also changed the shallow copy, because both still point to the same inner list. The deep copy is unaffected. Remember this when you copy a list of lists or a dictionary of dictionaries.

How do you count how often each item appears?

A very common small task. The standard library has Counter for exactly this.

PythonPySpark
from collections import Counter

words = "pune mumbai pune chennai pune mumbai".split()
counts = Counter(words)

print(counts)
print(counts.most_common(2))
Counter({'pune': 3, 'mumbai': 2, 'chennai': 1})
[('pune', 3), ('mumbai', 2)]

Pune appeared 3 times, Mumbai twice and Chennai once. If an interviewer asks you to do it without Counter, use a dictionary and counts[word] = counts.get(word, 0) + 1, as in the Python for data engineering guide.

How do you remove duplicates and keep the original order?

A set removes duplicates but loses order. A dictionary keeps insertion order, so its keys give you both.

PythonPySpark
cities = ["Pune", "Mumbai", "Pune", "Chennai", "Mumbai"]

print(list(set(cities)) == cities)
print(list(dict.fromkeys(cities)))
False
['Pune', 'Mumbai', 'Chennai']

dict.fromkeys(cities) keeps each city once, in the order it first appeared. The first line printed False because a set does not keep the original sequence. Practise this with the free Remove Duplicates Preserving Order problem.

What is a generator, and why use one?

A generator produces values one at a time, only when asked, instead of building the whole list first. For large data this saves memory.

PythonPySpark
def squares(n):
    for i in range(n):
        yield i * i

gen = squares(5)
print(next(gen))
print(next(gen))
print(list(gen))
0
1
[4, 9, 16]

The first 2 calls to next returned 0 and 1, and list(gen) collected the values that were still left (4, 9, 16). The function pauses at yield and continues from there next time. Generators are how Python reads a huge file line by line without loading it all.

How do you handle errors?

Put the risky code in try, handle the failure in except, and use finally for code that must always run.

PythonPySpark
def to_number(text):
    try:
        return int(text)
    except ValueError:
        return None
    finally:
        print("checked", repr(text))

print(to_number("42"))
print(to_number("n/a"))
checked '42'
42
checked 'n/a'
None

Both calls printed the "checked" line, because finally always runs. The second returned None instead of crashing. Catch the specific error you expect, here ValueError, rather than everything, so real bugs do not get hidden.

How to prepare

  1. Run every snippet above yourself and change a value to see what happens.
  2. Explain each answer out loud in two sentences, as if to a friend.
  3. Move on to problems: the free Python questions on Python practice check your code against hidden tests.
  4. For the next step, read pandas for beginners, because data engineering interviews move from plain Python to tables quickly.

Keep practising

Take what you just read into the editor.

Browse all 36 guides