38 Python Interview Questions for Freshers (with Answers and Code)
These are the Python questions freshers are asked in campus placements and first technical rounds, grouped the way interviewers group them: basics, data structures, OOP and practical (coding problems and everyday tasks). Each has a short, correct answer you can say out loud, and most have a runnable snippet with its output. Every snippet was run on Python 3.12 before publishing.
How to use this page: read a group, then open the matching cheat sheet and solve the linked interview problems in the browser. Interviewers always follow up with "what if...", so change each snippet and run it again; the practice editor runs Python with no install.
Related: Python projects for placement interviews (the project round), the 10-week roadmap for campus placements, and the free level test if you want to know which group to start with.
Basics (questions 1 to 10)
Cheat sheets for this group: Control flow and comprehensions, Functions and lambdas. Practise: Chunk an iterable (generator), A decorator that counts calls.
1. Is Python compiled or interpreted?
Both. CPython, the standard implementation, compiles source to bytecode (cached in .pyc files), then its interpreter executes that bytecode. You never run a separate compile step, so Python is usually called interpreted.
2. What is the difference between mutable and immutable types?
A mutable object can be changed in place: list, dict, set. An immutable object cannot: int, float, str, tuple, frozenset. "Changing" a string actually creates a new string object.
3. What is the difference between is and ==?
== compares values. is compares identity, meaning whether both names point to the same object in memory.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True same contents
print(a is b) # False two different list objects
print(a is c) # True same object
x = None
print(x is None) # True
Use is only for singletons like None. Never use it to compare numbers or strings; caching makes it work by accident sometimes.
4. What is the difference between /, // and %?
print(7 / 2) # 3.5 true division, always a float
print(7 // 2) # 3 floor division
print(-7 // 2) # -4 rounds down, not towards zero
print(-7 % 2) # 1 result takes the sign of the divisor
The negative case is the one interviewers ask about.
5. What does if __name__ == "__main__": do?
When a file is run directly, Python sets its __name__ to "__main__". When the same file is imported, __name__ is the module's name. The guard lets a file be both a script and an importable module: the test or demo code under it does not run on import.
def area(r):
return 3.14159 * r * r
if __name__ == "__main__":
print(area(2)) # runs only when this file is executed directly
6. How does Python pass arguments: by value or by reference?
Neither term fits. Python passes object references by value: the function gets a new name bound to the same object. Mutating that object is visible to the caller; rebinding the name is not.
def mutate(items):
items.append(4) # changes the caller's list
def rebind(items):
items = [99] # only rebinds the local name
nums = [1, 2, 3]
mutate(nums)
rebind(nums)
print(nums) # [1, 2, 3, 4]
7. What are *args and **kwargs?
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict.
def show(*args, **kwargs):
print(args, kwargs)
show(1, 2, city="Pune") # (1, 2) {'city': 'Pune'}
The *args and **kwargs lesson has exercises on unpacking in the other direction too.
8. What is the mutable default argument problem?
Default values are evaluated once, when the function is defined, not on every call. A mutable default is therefore shared across calls:
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("pen")) # ['pen']
print(add_item("book")) # ['pen', 'book'] the same list again
def add_item_fixed(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item_fixed("pen")) # ['pen']
print(add_item_fixed("book")) # ['book']
9. What is a lambda?
A small anonymous function limited to a single expression. It is most useful as a key argument:
students = [("Asha", 82), ("Vikram", 91), ("Neha", 76)]
print(sorted(students, key=lambda s: s[1], reverse=True))
# [('Vikram', 91), ('Asha', 82), ('Neha', 76)]
10. What is the difference between range, enumerate and zip?
range produces numbers; enumerate pairs each item with its index; zip walks two or more sequences in step. All three are lazy, so they cost almost no memory.
names = ["Asha", "Vikram"]
marks = [82, 91]
for i, (name, mark) in enumerate(zip(names, marks), start=1):
print(i, name, mark)
# 1 Asha 82
# 2 Vikram 91
If asked "how do you loop over a list with its index", the answer is enumerate, not range(len(x)). The enumerate, zip, range lesson covers the idioms.
Data structures (questions 11 to 20)
Cheat sheets for this group: Lists, Dicts and sets. Practise: Two sum, Group anagrams, Top K frequent elements, Valid parentheses.
11. What is the difference between a list and a tuple?
A list is mutable and uses square brackets; a tuple is immutable and uses round brackets. Because a tuple of immutable items is hashable, it can be a dictionary key or a set member, and a list cannot.
point = (3, 4)
distances = {point: 5.0}
print(distances[(3, 4)]) # 5.0
Use a tuple for a fixed record (coordinates, a row from a database), and a list for a collection that grows or changes.
12. Why is checking x in my_set faster than x in my_list?
A list is scanned element by element: O(n). A set or dict is a hash table, so Python hashes x and jumps straight to its slot: O(1) on average. The hashing and O(1) lookup lesson shows why, and the Big-O lesson covers how to state complexity in an interview.
import timeit
data_list = list(range(100_000))
data_set = set(data_list)
print(round(timeit.timeit(lambda: 99_999 in data_list, number=200), 3), "s list")
print(round(timeit.timeit(lambda: 99_999 in data_set, number=200), 5), "s set")
Measured on CPython 3.12.0: 0.139 s for 200 list lookups, 0.00001 s for 200 set lookups. Your numbers will differ, but the ratio will not. Say "O(n) versus O(1) average", then give the measurement.
13. What can be used as a dictionary key?
Any hashable object: strings, numbers, tuples of immutable items, frozensets. Lists, dicts and sets raise TypeError: unhashable type.
14. What is the difference between a shallow copy and a deep copy?
A shallow copy makes a new outer container that shares the inner objects. A deep copy copies everything recursively.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(99)
print(shallow) # [[1, 2, 99], [3, 4]]
print(deep) # [[1, 2], [3, 4]]
list.copy(), list(x) and slicing with x[:] are all shallow copies.
15. What is a list comprehension? And a dict comprehension?
A compact way to build a list (or dict, or set) from an iterable, optionally with a filter:
squares_of_evens = [n * n for n in range(10) if n % 2 == 0]
print(squares_of_evens) # [0, 4, 16, 36, 64]
marks = {"Asha": 82, "Vikram": 91, "Neha": 76}
passed = {name: m for name, m in marks.items() if m >= 80}
print(passed) # {'Asha': 82, 'Vikram': 91}
The list comprehension examples post has twenty more, including when not to use one.
16. What is the difference between sort() and sorted()?
list.sort() sorts in place and returns None. sorted() works on any iterable and returns a new list. Both take key and reverse, and both are stable.
nums = [3, 1, 2]
result = nums.sort()
print(result, nums) # None [1, 2, 3]
print(sorted("banana")) # ['a', 'a', 'a', 'b', 'n', 'n']
The classic trap is x = x.sort(), which sets x to None.
17. How do you count items in a list?
Three ways, in order of preference: collections.Counter, dict.get with a default, or setdefault.
from collections import Counter
words = "the cat and the hat and the bat".split()
print(Counter(words).most_common(2)) # [('the', 3), ('and', 2)]
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts["the"]) # 3
The Counter, defaultdict, namedtuple lesson covers the whole module.
18. How would you implement a stack and a queue in Python?
A stack is a list with append() and pop(), both O(1). A queue should be collections.deque, because list.pop(0) is O(n): it shifts every remaining element.
from collections import deque
stack = []
stack.append(1); stack.append(2)
print(stack.pop()) # 2 last in, first out
queue = deque()
queue.append("a"); queue.append("b")
print(queue.popleft()) # a first in, first out
The stacks lesson and the queues and deques lesson go from here to the interview problems.
19. What is the difference between dict.get(key) and dict[key]?
d[key] raises KeyError when the key is missing. d.get(key) returns None, or the default you pass. Use get when a missing key is normal, and [] when it is a bug you want to hear about.
20. What is a frozenset, and when would you use it?
An immutable set. Because it is hashable, it can be a dict key or a member of another set. Typical use: a set of tags as a key in a cache, or a set of sets.
groups = {frozenset({"python", "sql"}): "data", frozenset({"python", "flask"}): "web"}
print(groups[frozenset({"sql", "python"})]) # data
OOP (questions 21 to 29)
Cheat sheet for this group: Classes and OOP. Lessons: Classes and Objects, Inheritance and super(), Magic / Dunder Methods, Dataclasses.
21. What is self?
self is the instance a method was called on. obj.method() is effectively ClassName.method(obj), so Python passes the object as the first argument. The name self is a convention, not a keyword.
22. What is the difference between a class attribute and an instance attribute?
A class attribute is shared by every instance; an instance attribute belongs to one object. The classic trap is a mutable class attribute:
class Team:
members = [] # class attribute: shared
def __init__(self, name):
self.name = name # instance attribute: per object
a = Team("A")
b = Team("B")
a.members.append("Ravi")
print(b.members) # ['Ravi'] b sees a's change
Fix it by creating self.members = [] inside __init__.
23. What is the difference between __str__ and __repr__?
__str__ is the readable form used by print(). __repr__ is the unambiguous, developer-facing form used in the REPL and inside containers.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(2, 3)
print(p) # (2, 3)
print(repr(p)) # Point(x=2, y=3)
print([p]) # [Point(x=2, y=3)]
24. What does super() do?
It calls the next class in the method resolution order (MRO), usually the parent class:
class Animal:
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Woof, parent says " + super().speak()
print(Dog().speak()) # Woof, parent says ...
print([c.__name__ for c in Dog.__mro__]) # ['Dog', 'Animal', 'object']
25. What is the difference between @staticmethod and @classmethod?
A @classmethod receives the class as its first argument (cls) and is the standard way to write alternative constructors. A @staticmethod receives nothing extra; it is a plain function that lives in the class namespace because it belongs there logically.
class Date:
def __init__(self, d, m, y):
self.d, self.m, self.y = d, m, y
@classmethod
def from_string(cls, s): # "26/09/2026" -> Date
d, m, y = map(int, s.split("/"))
return cls(d, m, y)
@staticmethod
def is_leap(y):
return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
print(Date.from_string("26/09/2026").m) # 9
print(Date.is_leap(2024), Date.is_leap(2026)) # True False
26. What is @property?
A way to expose a method as an attribute, so callers write obj.area instead of obj.area(). It lets you add validation or computation later without changing the callers.
class Circle:
def __init__(self, r):
self._r = r
@property
def area(self):
return round(3.14159 * self._r ** 2, 2)
print(Circle(2).area) # 12.57
27. What is the difference between inheritance and composition?
Inheritance says "is a": SavingsAccount is an Account. Composition says "has a": a Car has an Engine. Prefer composition when the relationship is not truly "is a", because inheritance couples the child to every detail of the parent. Interviewers like hearing "favour composition over inheritance" with one concrete example.
28. How do you make objects comparable with ==?
Define __eq__. If you do, also define __hash__ (or accept that instances become unhashable), because equal objects must hash equally.
class Money:
def __init__(self, paise):
self.paise = paise
def __eq__(self, other):
return isinstance(other, Money) and self.paise == other.paise
def __hash__(self):
return hash(self.paise)
print(Money(100) == Money(100)) # True
print(len({Money(100), Money(100)})) # 1
29. What is a dataclass?
@dataclass writes __init__, __repr__ and __eq__ for you from the field annotations. Use it for classes that mainly hold data.
from dataclasses import dataclass
@dataclass
class Student:
name: str
marks: int = 0
s = Student("Asha", 82)
print(s) # Student(name='Asha', marks=82)
print(s == Student("Asha", 82)) # True
The dataclasses explainer covers frozen=True, field(default_factory=...) and ordering.
Practical (questions 30 to 38)
Cheat sheets for this group: Strings, Errors and exceptions, Files and JSON. Practise: Valid palindrome, First non-repeating character, Longest common prefix, Flatten a nested list.
30. What is a decorator?
A function that takes a function and returns a new function that wraps it, adding behaviour before or after the call. @name above a def is shorthand for func = name(func).
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}{args}")
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
return a + b
print(add(2, 3))
# calling add(2, 3)
# 5
print(add.__name__) # add
Without functools.wraps, add.__name__ would be "wrapper". Practise writing one in the decorators lesson or the call-counter problem.
31. What is a generator, and how is it different from a list?
A function containing yield returns a generator, which produces values one at a time, pausing between them. It uses almost no memory, but can be iterated only once and has no len() or indexing.
def evens(limit):
n = 0
while n < limit:
yield n
n += 2
g = evens(7)
print(next(g), next(g)) # 0 2
print(list(g)) # [4, 6] the rest; the generator is now exhausted
print(list(g)) # []
See the generators lesson and the chunked generator problem.
32. Explain try / except / else / finally.
try runs the risky code; except handles a named error; else runs only if nothing was raised; finally always runs, even after a return. Catch the most specific exception you can, never a bare except:.
def parse_age(text):
try:
age = int(text)
except ValueError:
return "not a number"
else:
return f"age {age}"
finally:
print("checked", repr(text))
print(parse_age("21")) # checked '21' then age 21
print(parse_age("abc")) # checked 'abc' then not a number
The try/except explainer covers custom exceptions and re-raising.
33. How do you read a file safely?
Use with, so the file closes even if an error occurs, and iterate line by line so a large file never needs to fit in memory.
from pathlib import Path
Path("marks.txt").write_text("Asha,82\nVikram,91\n", encoding="utf-8")
total = 0
with open("marks.txt", encoding="utf-8") as f:
for line in f:
name, mark = line.strip().split(",")
total += int(mark)
print(total) # 173
Always pass encoding="utf-8"; on Windows the default is not UTF-8. The context managers lesson explains what with actually calls.
34. What is the GIL?
The Global Interpreter Lock in CPython allows only one thread to execute Python bytecode at a time. So threads do not speed up CPU-heavy pure-Python work, but they still help with I/O-bound work (network calls, file reads), because the lock is released while waiting. For CPU-bound work, use multiprocessing. Python 3.12 still has the GIL. Python 3.13 added an optional free-threaded build without it, but it is a separate build, not the default.
35. Reverse a string, and check whether it is a palindrome
s = "Python"
print(s[::-1]) # nohtyP
print("".join(reversed(s))) # nohtyP
def is_palindrome(s):
cleaned = [ch.lower() for ch in s if ch.isalnum()]
return cleaned == cleaned[::-1]
print(is_palindrome("Madam")) # True
print(is_palindrome("A man, a plan, a canal: Panama")) # True
print(is_palindrome("placement")) # False
If asked to do it without building a reversed copy, use two pointers moving inward, which needs O(1) extra space. The two pointers lesson teaches this pattern and the valid palindrome problem checks your version.
def is_palindrome_in_place(s):
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
print(is_palindrome_in_place("racecar")) # True
print(is_palindrome_in_place("python")) # False
36. Two sum: find indices of two numbers that add up to a target
The brute-force answer checks every pair in O(n²). The expected answer uses a dict for O(n):
def two_sum(nums, target):
seen = {} # value -> index
for i, n in enumerate(nums):
if target - n in seen:
return [seen[target - n], i]
seen[n] = i
return []
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]
print(two_sum([3, 2, 4], 6)) # [1, 2]
Run your own version against the checker on the two sum problem page.
37. Find the duplicates in a list, and count characters
from collections import Counter
def find_duplicates(items):
seen, dupes = set(), set()
for x in items:
if x in seen:
dupes.add(x)
else:
seen.add(x)
return sorted(dupes)
data = [4, 1, 4, 2, 7, 2, 4]
print(find_duplicates(data)) # [2, 4]
print([x for x, count in Counter(data).items() if count > 1]) # [4, 2]
def char_count(s):
counts = {}
for ch in s:
counts[ch] = counts.get(ch, 0) + 1
return counts
print(char_count("banana")) # {'b': 1, 'a': 3, 'n': 2}
All of these are O(n). The first non-repeating character problem is the same idea with one more step.
38. FizzBuzz
def fizzbuzz(n):
if n % 15 == 0:
return "FizzBuzz"
if n % 3 == 0:
return "Fizz"
if n % 5 == 0:
return "Buzz"
return str(n)
print(" ".join(fizzbuzz(n) for n in range(1, 16)))
# 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
Check 15 first; if you test 3 first, 15 prints "Fizz". It is still asked, and people still get it wrong under pressure.
How to prepare
Follow-ups are always variations: "what if the list is sorted?", "can you do it without extra space?", "what is the complexity?". Run each snippet in the practice editor, break it, and fix it. Then solve fresh problems on the interview practice page: 24 classic problems grouped by pattern, each with a checker that runs in your browser and the complexity an interviewer expects. Keep the cheat sheets open while you do.
Projects cover the rest of the round; see Python projects for placement interviews. For a week-by-week plan that puts questions, problems and projects in order, see the Python roadmap for campus placements.