Complete Python Interview Questions and Practice Problems
This guide is a complete interview revision file for the Python Foundations series.
It covers the ideas from the earlier lessons:
- Python basics, variables, input, output, and type conversion
- operators, control flow, loops, and pattern thinking
- strings, lists, tuples, sets, and dictionaries
- functions, recursion, time complexity, and memory behavior
- OOP, encapsulation, inheritance, polymorphism, and abstraction
- file handling, JSON, pickle, exceptions, iterators, generators, namespaces, closures, decorators, modules, imports, and packages
All questions, explanations, examples, and practice problems here are written freshly. Use this as a learning and interview-preparation guide, not as a memorization sheet.
How to Use This Interview Guide
Use three passes:
- Read the question and answer it out loud before looking.
- Compare your answer with the explanation.
- Run or mentally trace the code where shown.
In interviews, the best answers are usually:
- clear
- short at first
- backed by one example
- honest about edge cases
- connected to practical use
Interview Answer Formula
For theory questions, use this structure:
- Define the concept.
- Mention why it matters.
- Give a small example.
- Mention one common mistake or edge case.
Example:
A list is an ordered, mutable collection in Python.
It is useful when we need to store items and update them later.
For example, scores = [10, 20, 30].
A common mistake is copying a nested list with copy(), because nested objects are still shared.Part 1: Core Python Interview Questions
1. What is Python?
Python is a high-level, general-purpose programming language known for readable syntax and a large standard library.
It is used for web development, automation, data analysis, machine learning, scripting, testing, backend services, and many other tasks.
2. Why is Python called high-level?
Python hides many low-level details such as manual memory management and direct CPU instructions.
You write code closer to human-readable logic:
name = "Asha"
print(f"Welcome, {name}")Explanation
- The variable
nameis assigned the string value "Asha". - The
printfunction outputs a formatted string that includes the value ofname. - The f-string syntax (prefixing the string with
f) allows for easy insertion of variable values directly within the string. - The output of this code will be: "Welcome, Asha".
3. Is Python interpreted or compiled?
Python is commonly called interpreted because users usually run source code directly.
In CPython, Python source code is first compiled internally into bytecode, and then that bytecode is executed by the Python virtual machine.
4. What is CPython?
CPython is the reference implementation of Python, written mainly in C.
When most people say "Python", they usually mean CPython unless they mention another implementation such as PyPy or Jython.
5. What is dynamic typing?
Dynamic typing means variable names do not have fixed declared types.
value = 10
value = "ten"
print(value)Explanation
- The variable
valueis initially assigned an integer of 10. - The variable
valueis then reassigned to the string "ten", overriding the previous integer value. - The
printfunction outputs the current value of the variable, which will display "ten". - This snippet illustrates how Python allows dynamic typing, enabling variables to change types during execution.
The name value first points to an integer object and later points to a string object.
6. What is strong typing?
Strong typing means Python does not silently mix incompatible types in unsafe ways.
age = 25
print("Age: " + str(age))Explanation
- The variable
ageis assigned the integer value of 25. - The
printfunction is used to output a string that includes the age. - The integer
ageis converted to a string usingstr(age)to ensure proper concatenation. - The final output will display "Age: 25" in the console.
Without str(age), string concatenation with an integer would fail.
7. What is PEP 8?
PEP 8 is Python's style guide.
It gives naming, formatting, indentation, spacing, and readability conventions. It is not the language grammar, but it helps teams write consistent code.
8. Why is indentation important in Python?
Indentation defines code blocks in Python.
score = 85
if score >= 50:
print("pass")
else:
print("try again")Explanation
- The variable
scoreis initialized with a value of 85. - An
ifstatement checks if thescoreis greater than or equal to 50. - If the condition is true, it prints "pass"; otherwise, it prints "try again".
- This code effectively categorizes the score into a pass/fail outcome based on a threshold.
Wrong indentation can change meaning or raise an error.
9. What are keywords?
Keywords are reserved words with special meaning.
Examples include if, else, for, while, def, class, try, except, return, yield, import, global, and nonlocal.
10. What are identifiers?
Identifiers are names created by programmers for variables, functions, classes, modules, and other objects.
Good identifiers are descriptive:
completed_lessons = 12Explanation
- The variable
completed_lessonsis assigned the integer value of 12. - This could represent the total number of lessons a user has finished in an educational application.
- The variable can be used later in the code for calculations, condition checks, or displaying progress.
- It serves as a simple way to store and manipulate user progress data.
11. What does print() do?
print() writes text representation of objects to standard output.
print("Python", "Interview", sep=" - ", end="!\n")Explanation
- The
printfunction is used to display text output to the console. - The
sepparameter specifies the string inserted between multiple arguments, here it is set to " - ". - The
endparameter defines what is printed at the end of the output; in this case, it adds an exclamation mark followed by a newline. - The output of this code will be:
Python - Interview!
Output:
Python - Interview!12. What does input() return?
input() always returns a string.
raw_age = input("Age: ")
age = int(raw_age)Explanation
- The
input()function prompts the user to enter their age as a string. - The entered value is stored in the variable
raw_age. - The
int()function converts the string value ofraw_ageinto an integer and assigns it to the variableage. - This conversion allows for numerical operations to be performed on the age value later in the program.
Convert it when you need a number.
13. What is type conversion?
Type conversion changes a value from one type to another.
price = "199"
quantity = 3
print(int(price) * quantity)Explanation
- The variable
priceis initialized as a string containing the value "199". - The variable
quantityis set to an integer value of 3. - The
int(price)function converts the stringpriceto an integer for mathematical operations. - The
printfunction outputs the result of multiplying the integer value ofpricebyquantity, which is 597.
14. What is implicit type conversion?
Implicit conversion happens when Python safely converts one type during an expression.
result = 10 + 2.5
print(result)Explanation
- The variable
resultis assigned the sum of an integer (10) and a float (2.5). - Python automatically handles the addition of different numeric types, resulting in a float.
- The
printfunction outputs the value ofresultto the console, which will be 12.5. - This snippet illustrates how Python can perform arithmetic operations seamlessly.
Output:
12.515. What is explicit type conversion?
Explicit conversion is done manually with functions such as int(), float(), str(), list(), tuple(), set(), and dict().
16. What does a Python variable store?
A Python variable stores a reference to an object, not the object directly.
first = [1, 2]
second = first
second.append(3)
print(first)Explanation
- The variable
firstis initialized as a list containing two integers: 1 and 2. - The variable
secondis assigned to reference the same list object asfirst, not a copy. - When
3is appended tosecond, it modifies the original list that bothfirstandsecondreference. - The
print(first)statement outputs the modified list, which now includes the appended value, resulting in[1, 2, 3]. - This demonstrates how mutable objects like lists in Python can be affected by changes through multiple references.
Output:
[1, 2, 3]Both names point to the same list.
17. What is object identity?
Object identity is the unique identity of an object during its lifetime.
Use id() to inspect it:
items = ["python"]
print(id(items))Explanation
- The code initializes a list named
itemscontaining a single string element "python". - The
id()function is called withitemsas an argument, which returns the unique memory address of the list object. - The
print()function outputs this memory address to the console, allowing you to see where the list is stored in memory. - This can be useful for understanding object identity and memory management in Python.
18. What is the difference between == and is?
== compares values.
is compares object identity.
a = [1, 2]
b = [1, 2]
c = a
print(a == b)
print(a is b)
print(a is c)Explanation
- The code initializes two lists,
aandb, with identical contents, and assignscto reference the same list asa. - The expression
a == bchecks for value equality, returningTruesince both lists contain the same elements. - The expression
a is bchecks for identity, returningFalsebecauseaandbare two distinct objects in memory. - The expression
a is cchecks for identity, returningTruesincecreferences the same object asa.
Output:
True
False
True19. What are mutable objects?
Mutable objects can be changed after creation.
Examples include lists, dictionaries, and sets.
20. What are immutable objects?
Immutable objects cannot be changed after creation.
Examples include integers, floats, strings, tuples, booleans, and frozensets.
21. Why are strings immutable?
When you "modify" a string, Python creates a new string.
title = "python"
updated = title.upper()
print(title)
print(updated)Explanation
- The variable
titleis initialized with the string "python". - The
upper()method is called ontitle, converting it to uppercase and storing the result in the variableupdated. - The original string
titleis printed, displaying "python". - The uppercase version stored in
updatedis printed, displaying "PYTHON".
Output:
python
PYTHON22. What is truthy and falsy?
Python treats some values as false in conditions.
Falsy examples:
FalseNone00.0""[]{}set()
Non-empty containers and non-zero numbers are usually truthy.
23. What is None?
None represents absence of a value.
It is not the same as 0, False, or an empty string.
result = None
if result is None:
print("No result yet")Explanation
- Initializes a variable
resultwith a value ofNone. - Uses an
ifstatement to check ifresultis stillNone. - If the condition is true, it prints "No result yet" to the console.
- This code can be useful for debugging or handling cases where a result is expected but not yet available.
24. What is the difference between type() and isinstance()?
type() gives the exact class of an object.
isinstance() checks whether an object belongs to a class or subclass.
print(type(True))
print(isinstance(True, int))Explanation
- The
print(type(True))statement outputs the type of the Boolean valueTrue, which is<class 'bool'>. - The
print(isinstance(True, int))statement checks ifTrueis an instance of theintclass, returningTruesince in Python,Trueis treated as1. - This code demonstrates the dual nature of Boolean values in Python, where they are subclasses of integers.
- It highlights how Boolean values can be used interchangeably with integers in certain contexts, such as arithmetic operations.
Output:
<class 'bool'>
True25. What is floor division?
Floor division uses // and returns the floor of the division result.
print(7 // 2)
print(-7 // 2)Explanation
- The
//operator performs floor division, which returns the largest integer less than or equal to the division result. - The first line
print(7 // 2)calculates the floor division of 7 by 2, resulting in 3. - The second line
print(-7 // 2)calculates the floor division of -7 by 2, resulting in -4, demonstrating how floor division rounds down towards negative infinity. - This behavior is important for understanding how Python handles division with negative numbers compared to other programming languages.
Output:
3
-426. What is the modulo operator?
% returns the remainder after division.
print(17 % 5)Explanation
- The
printfunction outputs the result of the expression inside it to the console. - The expression
17 % 5uses the modulus operator%, which returns the remainder of the division of 17 by 5. - In this case, 17 divided by 5 equals 3 with a remainder of 2, so the output will be
2. - This operation is useful for determining if a number is even or odd, or for cyclic calculations.
Output:
227. What is operator precedence?
Operator precedence decides which part of an expression is evaluated first.
print(2 + 3 * 4)
print((2 + 3) * 4)Explanation
- The first print statement calculates
2 + 3 * 4, where multiplication has higher precedence than addition, resulting in2 + 12, which equals14. - The second print statement calculates
(2 + 3) * 4, where the parentheses force the addition to be performed first, resulting in5 * 4, which equals20. - This snippet demonstrates how operator precedence and parentheses can affect the outcome of arithmetic operations in Python.
- It highlights the importance of understanding the order of operations to avoid unexpected results in calculations.
Output:
14
2028. What is short-circuit evaluation?
Python may stop evaluating a logical expression once the result is already known.
name = ""
if name and name[0] == "A":
print("Starts with A")
else:
print("No usable name")Explanation
- Initializes an empty string variable
name. - Uses a conditional statement to check if
nameis non-empty and if its first character is 'A'. - If both conditions are true, it prints "Starts with A".
- If either condition is false, it prints "No usable name".
Because name is empty, name[0] is not evaluated.
29. What is a conditional expression?
It is Python's compact if-else expression.
score = 72
status = "pass" if score >= 50 else "fail"
print(status)Explanation
- A variable
scoreis initialized with a value of 72. - A conditional expression assigns "pass" to the variable
statusifscoreis 50 or higher; otherwise, it assigns "fail". - The
printfunction outputs the value ofstatus, which will be "pass" in this case.
30. What is the difference between for and while loops?
Use for when you are iterating over a known iterable.
Use while when the loop depends on a condition that may change.
31. What are break, continue, and pass?
break exits the loop.
continue skips to the next iteration.
pass does nothing and is used as a placeholder.
32. What does range() produce?
range() produces an immutable sequence-like object that generates numbers lazily.
for number in range(2, 7, 2):
print(number)Explanation
- The
range(2, 7, 2)function generates a sequence of numbers starting from 2 up to, but not including, 7, with a step of 2. - The loop iterates through the generated numbers, which are 2, 4, and 6.
- The
print(number)statement outputs each number in the sequence to the console. - This code effectively showcases how to work with ranges and control the increment in a loop.
Output:
2
4
633. What is loop else?
A loop else block runs when the loop finishes normally, without break.
target = 7
numbers = [2, 4, 6]
for number in numbers:
if number == target:
print("found")
break
else:
print("not found")Explanation
- The variable
targetis set to 7, which is the number the code will search for in the listnumbers. - The list
numberscontains three integers: 2, 4, and 6. - A
forloop iterates through eachnumberin thenumberslist. - If a
numbermatches thetarget, it prints "found" and exits the loop usingbreak. - If the loop completes without finding the target, the
elseblock executes, printing "not found".
34. What are membership operators?
in and not in test membership.
tags = ["python", "oop", "files"]
print("oop" in tags)Explanation
- A list named
tagsis created containing three string elements: "python", "oop", and "files". - The
printfunction is used to output the result of the expression"oop" in tags. - The expression evaluates to
Trueif "oop" is found in thetagslist, andFalseotherwise. - This demonstrates the use of the
inkeyword for membership testing in Python lists.
35. What are bitwise operators?
Bitwise operators work on integer bits.
Common examples are &, |, ^, ~, <<, and >>.
They are useful in flags, masks, permissions, and low-level tasks.
36. How does string indexing work?
Strings are indexed from 0.
Negative indexes count from the end.
word = "python"
print(word[0])
print(word[-1])Explanation
- The variable
wordis assigned the string value "python". print(word[0])outputs the first character of the string, which is 'p'.print(word[-1])outputs the last character of the string, which is 'n'.- This code demonstrates how to use positive and negative indexing to access string elements.
Output:
p
n37. How does slicing work?
Slicing uses start:stop:step.
word = "interview"
print(word[0:5])
print(word[::-1])Explanation
- The variable
wordis initialized with the string "interview". - The first
printstatement outputs the first five characters of the string, resulting in "inter". - The second
printstatement uses slicing with a step of -1 to reverse the string, producing "weivretni".
Output:
inter
weivretni38. Why should you prefer join() for combining many strings?
Repeated string concatenation can create many intermediate strings.
join() is clearer and usually more efficient for many pieces.
parts = ["python", "is", "clear"]
sentence = " ".join(parts)
print(sentence)Explanation
- A list named
partsis created containing three string elements: "python", "is", and "clear". - The
join()method is used on a space character to concatenate the elements of the list into a single string, with spaces in between. - The resulting string is stored in the variable
sentence. - Finally, the
print()function outputs the concatenated sentence to the console.
39. What is the difference between find() and index() on strings?
find() returns -1 if the substring is missing.
index() raises ValueError if the substring is missing.
40. What do split() and strip() do?
strip() removes surrounding whitespace or selected characters.
split() breaks a string into a list.
line = " python,oop,files "
print(line.strip().split(","))Explanation
- The
linevariable contains a string with leading and trailing spaces and comma-separated values. - The
strip()method removes any whitespace from the beginning and end of the string. - The
split(",")method then divides the cleaned string into a list using the comma as a delimiter. - The final output is a list of strings:
['python', 'oop', 'files'], with no extra spaces.
41. What is a list?
A list is an ordered, mutable collection.
scores = [90, 75, 88]
scores.append(92)
print(scores)Explanation
- A list named
scoresis initialized with three integer values representing scores. - The
appendmethod is used to add a new score,92, to the end of thescoreslist. - The
printfunction outputs the updated list, which now contains four scores:[90, 75, 88, 92].
42. What is the difference between append() and extend()?
append() adds one object as a single item.
extend() adds each item from an iterable.
items = [1, 2]
items.append([3, 4])
print(items)
items = [1, 2]
items.extend([3, 4])
print(items)Explanation
- The
appendmethod adds its argument as a single element to the end of the list, resulting in a nested list when a list is appended. - In the first case,
items.append([3, 4])results initemsbeing[1, 2, [3, 4]]. - The
extendmethod, on the other hand, iterates over its argument and adds each element to the list, effectively flattening the input. - In the second case,
items.extend([3, 4])results initemsbeing[1, 2, 3, 4]. - This distinction is crucial when manipulating lists in Python, as it affects the structure of the resulting list.
43. What is list aliasing?
Aliasing happens when two names refer to the same list.
primary = ["draft"]
alias = primary
alias.append("review")
print(primary)Explanation
- The variable
primaryis initialized as a list containing a single string element "draft". - The variable
aliasis assigned to reference the same list object asprimary, meaning both variables point to the same memory location. - When "review" is appended to
alias, it modifies the list that bothprimaryandaliasreference. - The
print(primary)statement outputs the modified list, which now contains both "draft" and "review".
44. How do you copy a list?
For a shallow copy, use copy(), slicing, or list().
original = ["a", "b"]
clone = original.copy()Explanation
- The variable
originalis initialized as a list containing two string elements: "a" and "b". - The
copy()method is called on theoriginallist to create a new list,clone, which contains the same elements. - The
clonelist is a shallow copy, meaning changes toclonewill not affectoriginal, and vice versa. - This method is useful for preserving the original list while allowing modifications to the copy.
For nested mutable objects, use copy.deepcopy() when you need independent nested data.
45. What is a tuple?
A tuple is an ordered, immutable collection.
It is useful for fixed records, multiple return values, and dictionary keys when all items are hashable.
46. Why does a one-item tuple need a comma?
The comma creates the tuple, not the parentheses.
not_tuple = (10)
one_item_tuple = (10,)
print(type(not_tuple))
print(type(one_item_tuple))Explanation
- The variable
not_tupleis assigned a single integer value, which does not create a tuple. - The variable
one_item_tupleis correctly defined as a tuple containing one item by including a trailing comma. - The
type()function is used to check the data types of both variables. - The output will show that
not_tupleis of typeint, whileone_item_tupleis of typetuple. - This snippet highlights the importance of the comma in tuple creation when defining single-item tuples in Python.
47. What is tuple unpacking?
Tuple unpacking assigns values from a tuple-like iterable to names.
name, score = ("Neha", 91)
print(name)
print(score)Explanation
- A tuple containing a name and a score is created and assigned to the variables
nameandscoresimultaneously. - The first element of the tuple, "Neha", is assigned to the variable
name. - The second element, 91, is assigned to the variable
score. - The
printstatements output the values ofnameandscoreto the console.
48. What is a set?
A set is an unordered collection of unique hashable items.
topics = {"python", "oop", "python"}
print(topics)Explanation
- A set named
topicsis initialized with three elements: "python", "oop", and "python". - Sets in Python automatically eliminate duplicate entries, so "python" appears only once in the final set.
- The
printfunction outputs the contents of thetopicsset to the console. - The order of elements in a set is not guaranteed, as sets are unordered collections.
49. Why are lists not allowed inside sets?
Set elements must be hashable.
Lists are mutable and unhashable, so they cannot be set elements.
50. What is a dictionary?
A dictionary maps keys to values.
Keys must be hashable. Values can be almost any object.
profile = {"name": "Ira", "score": 88}
print(profile["name"])Explanation
- A dictionary named
profileis created with two key-value pairs: "name" and "score". - The value associated with the key "name" is accessed using
profile["name"]. - The
printfunction outputs the value of "name", which is "Ira", to the console.
51. Why does dictionary membership check keys?
in checks dictionary keys because keys are the lookup mechanism.
profile = {"name": "Ira", "score": 88}
print("name" in profile)
print("Ira" in profile)Explanation
- A dictionary named
profileis created with two key-value pairs: "name" and "score". - The first print statement checks if the key "name" exists in the
profiledictionary, returningTrue. - The second print statement checks if the value "Ira" exists in the
profiledictionary, returningFalsesince it is not a key.
52. What are dictionary views?
keys(), values(), and items() return dynamic view objects.
They reflect dictionary changes.
53. When should you use a list, tuple, set, or dictionary?
Use a list for ordered mutable sequences.
Use a tuple for fixed records.
Use a set for uniqueness and membership tests.
Use a dictionary for key-value lookup.
54. What is a function?
A function is a reusable block of code that can receive inputs and return output.
def add_bonus(score, bonus):
return score + bonusExplanation
- The function
add_bonustakes two parameters:scoreandbonus. - It returns the sum of
scoreandbonus, effectively increasing the score by the specified bonus amount. - This function can be used in scoring systems where additional points are awarded, such as in games or assessments.
- It is a simple and reusable function that promotes code clarity and modularity.
55. What is the difference between parameter and argument?
A parameter is the name in the function definition.
An argument is the value passed during the function call.
56. What is the difference between print and return?
print displays a value.
return sends a value back to the caller.
def double(number):
return number * 2
result = double(5)
print(result)Explanation
- The
doublefunction takes a single parameter,number. - It returns the value of
numbermultiplied by 2. - The function is called with the argument
5, and the result is stored in the variableresult. - Finally, the value of
resultis printed, which outputs10.
57. What does a function return if there is no return statement?
It returns None.
def greet():
print("hello")
value = greet()
print(value)Explanation
- The
greetfunction is defined to print the string "hello" when called. - The function is invoked with
value = greet(), which executes the print statement inside the function. - The return value of the
greetfunction isNonesince it does not explicitly return anything, which is stored in the variablevalue. - The final print statement outputs the value of
value, which will beNone.
58. What are default arguments?
Default arguments are used when the caller does not provide a value.
def create_title(text, prefix="Lesson"):
return f"{prefix}: {text}"Explanation
- The function
create_titletakes two parameters:text(the main title) andprefix(defaulting to "Lesson"). - It uses an f-string to concatenate the prefix and the text, ensuring a consistent format.
- The return value is a string that combines the prefix and the text, separated by a colon.
- This function can be useful for creating standardized titles in educational or instructional content.
59. Why are mutable default arguments risky?
The default object is created once when the function is defined, not each time it is called.
Safer version:
def add_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tagsExplanation
- The function
add_tagtakes two parameters:tag(the tag to be added) andtags(an optional list of existing tags). - If
tagsis not provided (i.e., it isNone), a new empty list is created to hold the tags. - The specified
tagis appended to thetagslist. - The updated list of tags is returned, allowing for easy accumulation of tags over multiple function calls.
- This approach ensures that each call to
add_tagcan either modify an existing list or create a new one if none is provided.
60. What are *args and **kwargs?
*args collects extra positional arguments.
**kwargs collects extra keyword arguments.
def describe_event(*labels, **details):
return labels, details
print(describe_event("python", "live", speaker="Maya"))Explanation
- The function
describe_eventaccepts a variable number of positional arguments (*labels) and keyword arguments (**details). - Positional arguments are collected into a tuple named
labels, while keyword arguments are stored in a dictionary nameddetails. - The function returns both
labelsanddetails, allowing for flexible input regarding event characteristics. - In the provided print statement, the function is called with two positional arguments ("python", "live") and one keyword argument (speaker="Maya").
61. What is a lambda function?
A lambda is a small anonymous function expression.
scores = [3, 10, 2]
print(sorted(scores, key=lambda value: value % 3))Explanation
- The list
scorescontains three integer values: 3, 10, and 2. - The
sorted()function is used to sort the list, with a custom sorting key defined by a lambda function. - The lambda function calculates the remainder of each score when divided by 3 (
value % 3). - The sorting will arrange the scores in ascending order based on these remainders.
- The output will be a new list of scores sorted according to their remainders: [3, 10, 2] becomes [3, 2, 10].
Use normal def when logic needs a name, documentation, or multiple statements.
62. What are first-class functions?
Functions are first-class objects in Python.
You can assign them to variables, pass them as arguments, return them from functions, and store them in containers.
63. What is scope?
Scope decides where a name can be accessed.
Python follows LEGB lookup:
- Local
- Enclosing
- Global
- Built-in
64. What is the global keyword?
global tells Python that assignment should target a module-level name.
Use it carefully because it can make code harder to reason about.
65. What is the nonlocal keyword?
nonlocal lets a nested function assign to a name from an enclosing function scope.
def make_counter():
count = 0
def next_count():
nonlocal count
count += 1
return count
return next_countExplanation
- The
make_counterfunction initializes a local variablecountto zero. - It defines an inner function
next_countthat increments thecountvariable and returns its updated value. - The
nonlocalkeyword allows the inner function to modify thecountvariable from the enclosing scope. - The
make_counterfunction returns thenext_countfunction, enabling the creation of multiple independent counter instances. - Each call to the returned function will increase the count, demonstrating the concept of closures in Python.
66. What is a closure?
A closure is a function that remembers values from a surrounding function even after that surrounding function has finished.
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
triple = make_multiplier(3)
print(triple(4))Explanation
- The
make_multiplierfunction takes a single argumentfactorand defines an inner functionmultiply. - The
multiplyfunction takes anumberand returns the product ofnumberandfactor. make_multiplierreturns themultiplyfunction, effectively creating a closure that retains thefactorvalue.- The variable
tripleis assigned the result ofmake_multiplier(3), creating a function that triples its input. - The
print(triple(4))statement outputs12, which is the result of multiplying4by3.
67. What is a decorator?
A decorator wraps a function to add behavior before, after, or around the original function.
import functools
def announce(function_to_call):
@functools.wraps(function_to_call)
def wrapper(*args, **kwargs):
print("starting")
return function_to_call(*args, **kwargs)
return wrapperExplanation
- The
announcefunction is a decorator that takes another function as an argument. - It uses
functools.wrapsto preserve the metadata of the original function being wrapped. - Inside the
wrapperfunction, it prints "starting" before calling the original function with any provided arguments. - The
wrapperfunction is returned, effectively replacing the original function with the new behavior when the decorator is applied.
68. Why use functools.wraps in decorators?
It preserves the wrapped function's name, docstring, annotations, and other metadata.
This helps debugging, testing, documentation, and frameworks.
69. What is recursion?
Recursion is when a function calls itself to solve a smaller version of the same problem.
Every recursive solution needs:
- a base case
- progress toward the base case
70. What is a base case?
A base case is the stopping condition in recursion.
def factorial(number):
if number == 0:
return 1
return number * factorial(number - 1)Explanation
- The function
factorialtakes a single argumentnumber. - It checks if
numberis 0; if true, it returns 1, as the factorial of 0 is defined as 1. - If
numberis greater than 0, it recursively calls itself withnumber - 1and multiplies the result bynumber. - This process continues until it reaches the base case of 0, effectively calculating the factorial through repeated multiplication.
- The function demonstrates the concept of recursion, where a function calls itself to solve smaller instances of the same problem.
71. What is memoization?
Memoization stores results of expensive function calls so repeated calls can reuse the answer.
def fibonacci(number, cache=None):
if cache is None:
cache = {}
if number in cache:
return cache[number]
if number <= 1:
return number
cache[number] = fibonacci(number - 1, cache) + fibonacci(number - 2, cache)
return cache[number]Explanation
- Defines a recursive function
fibonaccithat computes the Fibonacci number for a given inputnumber. - Utilizes a
cachedictionary to store previously computed Fibonacci numbers, enhancing performance by avoiding redundant calculations. - Checks if the
cacheisNoneand initializes it to an empty dictionary if so, ensuring that memoization works correctly. - Returns the Fibonacci number directly from the
cacheif it has already been computed, significantly reducing the time complexity. - Handles base cases where the input
numberis 0 or 1, returning the number itself in those scenarios.
72. What is Big O notation?
Big O describes how runtime or memory grows as input size grows.
It ignores machine-specific details and focuses on growth pattern.
73. What are common time complexities?
Common examples:
| Complexity | Meaning |
|---|---|
O(1) | constant time |
O(log n) | logarithmic time |
O(n) | linear time |
O(n log n) | common efficient sorting time |
O(n^2) | nested loop over same input |
74. What is space complexity?
Space complexity describes how extra memory grows with input size.
A function that creates a new list of size n usually has O(n) extra space.
75. What is aliasing?
Aliasing means multiple names point to the same object.
This matters most with mutable objects.
76. What is the difference between shallow copy and deep copy?
A shallow copy creates a new outer container but keeps references to nested objects.
A deep copy recursively copies nested objects.
import copy
matrix = [[1], [2]]
shallow = matrix.copy()
deep = copy.deepcopy(matrix)
matrix[0].append(99)
print(shallow)
print(deep)Explanation
- The code imports the
copymodule to utilize its copying functionalities. - A 2D list
matrixis created with two sublists containing integers. - A shallow copy of
matrixis made using thecopy()method, which copies the outer list but references the inner lists. - A deep copy of
matrixis created usingcopy.deepcopy(), which creates a new list and recursively copies all inner lists. - Modifying the first sublist of
matrixby appending99affects the shallow copy but not the deep copy, demonstrating the difference in how they handle nested objects.
77. What is garbage collection?
Garbage collection frees memory used by objects that are no longer reachable.
CPython mainly uses reference counting and also has a cyclic garbage collector.
78. What is hashability?
An object is hashable if it has a stable hash value during its lifetime and can be compared for equality.
Hashable objects can be dictionary keys and set elements.
79. What is a class?
A class is a blueprint for creating objects with related data and behavior.
class Lesson:
def __init__(self, title):
self.title = titleExplanation
- The
Lessonclass is defined to encapsulate the concept of a lesson. - The
__init__method is a constructor that initializes a new instance of the class. - The constructor takes one parameter,
title, which is assigned to the instance variableself.title. - This allows each lesson object to have a unique title when created.
80. What is an object?
An object is an instance of a class.
lesson = Lesson("Decorators")
print(lesson.title)Explanation
- A
Lessonobject is instantiated with the title "Decorators". - The
titleattribute of thelessonobject is accessed and printed to the console. - This showcases basic object-oriented programming principles in Python, specifically attribute access.
- The output will display the string "Decorators" when the code is executed.
81. What is self?
self is the conventional name for the current object inside instance methods.
Python passes it automatically when you call a method on an object.
82. What is __init__?
__init__ initializes a newly created object.
It is not a constructor in the exact low-level sense, but beginners can think of it as setup logic for an object.
83. What is encapsulation?
Encapsulation means keeping data and behavior together and controlling access through methods or properties.
It helps protect object state.
84. What is name mangling?
Names that start with two underscores inside a class are rewritten by Python to include the class name.
This reduces accidental name collisions in subclasses.
85. What is a property?
property lets method-like logic be accessed like an attribute.
class Progress:
def __init__(self, completed, total):
self.completed = completed
self.total = total
@property
def percent(self):
return round(self.completed / self.total * 100)Explanation
- The
Progressclass is initialized with two parameters:completedandtotal, representing the number of completed tasks and the total tasks, respectively. - The
percentproperty calculates the completion percentage by dividingcompletedbytotal, multiplying by 100, and rounding the result to the nearest whole number. - The use of the
@propertydecorator allowspercentto be accessed like an attribute, providing a clean interface for retrieving the completion percentage without needing to call it as a method. - This class can be useful for tracking progress in various applications, such as project management or task completion systems.
86. What is a class variable?
A class variable belongs to the class and is shared through instances unless shadowed by an instance attribute.
87. What is an instance variable?
An instance variable belongs to a specific object.
It is usually created with self.name = value.
88. What is a static method?
A static method is a function placed inside a class namespace that does not receive self or cls.
Use it for helper behavior closely related to the class.
89. What is a class method?
A class method receives the class as cls.
It is useful for alternate constructors and class-level behavior.
90. What is inheritance?
Inheritance lets one class reuse or extend behavior from another class.
The child class is a specialized form of the parent class.
91. What does super() do?
super() gives access to parent-class behavior according to Python's method resolution order.
It is commonly used to call the parent __init__.
92. What is method overriding?
Method overriding happens when a child class defines a method with the same name as a parent method.
The child version is used for child objects.
93. What is polymorphism?
Polymorphism means different objects can respond to the same method call in their own way.
class TextLesson:
def render(self):
return "text"
class VideoLesson:
def render(self):
return "video"
for item in [TextLesson(), VideoLesson()]:
print(item.render())Explanation
- Defines two classes,
TextLessonandVideoLesson, each with arendermethod that returns a string indicating the type of lesson. - The
forloop iterates over a list containing instances of both classes. - Each lesson's
rendermethod is called, showcasing how different classes can be treated uniformly through their shared interface. - The output will be "text" followed by "video", illustrating the concept of polymorphism in object-oriented programming.
94. What is abstraction?
Abstraction means exposing essential behavior while hiding implementation details.
Abstract base classes can define required methods for subclasses.
95. What is composition?
Composition means building one object using other objects.
Use composition when an object "has a" helper object.
Inheritance is better when an object truly "is a" specialized version of another object.
96. What are special methods?
Special methods are double-underscore methods such as __str__, __len__, __add__, and __iter__.
They let your objects work naturally with Python syntax and built-ins.
97. What is file handling?
File handling means reading from and writing to files.
Use a context manager so files close automatically:
from pathlib import Path
path = Path("notes.txt")
text = path.read_text(encoding="utf-8")Explanation
- Imports the
Pathclass from thepathlibmodule, which provides an object-oriented approach to handling filesystem paths. - Creates a
Pathobject for the file named "notes.txt". - Uses the
read_textmethod of thePathobject to read the entire contents of the file as a string, specifying UTF-8 encoding for proper text handling. - The resulting string is stored in the variable
text, which can be used for further processing or analysis.
98. What are common file modes?
Common modes:
| Mode | Meaning |
|---|---|
r | read text |
w | write text and replace existing content |
a | append text |
rb | read binary |
wb | write binary |
99. What is serialization?
Serialization converts an object into a format that can be stored or transmitted.
JSON serialization is common for simple structured data.
100. What is the difference between JSON and pickle?
JSON is text-based, language-independent, and safer for data exchange.
Pickle is Python-specific and can preserve more Python objects, but loading untrusted pickle data is unsafe.
101. What is an exception?
An exception is an error event that interrupts normal program flow.
You can handle expected errors with try and except.
102. Why should you catch specific exceptions?
Catching specific exceptions avoids hiding unrelated bugs.
try:
score = int("92")
except ValueError:
score = 0Explanation
- The code attempts to convert a string "92" into an integer and assigns it to the variable
score. - If the conversion fails due to a
ValueError, it catches the exception and assigns0toscore. - This ensures that the program can handle invalid inputs gracefully without crashing.
- The use of
tryandexceptblocks is a common practice for managing exceptions in Python.
103. What do else and finally do in exception handling?
else runs only if the try block succeeds.
finally runs whether an exception happened or not.
104. How do you raise a custom exception?
Create a class that inherits from Exception.
class InvalidProgressError(Exception):
pass
def set_progress(value):
if not 0 <= value <= 100:
raise InvalidProgressError("progress must be between 0 and 100")Explanation
- Defines a custom exception class
InvalidProgressErrorthat inherits from the built-inExceptionclass. - The function
set_progresstakes a parametervalueand checks if it is within the range of 0 to 100. - If the
valueis outside this range, it raises theInvalidProgressErrorwith a descriptive message. - This implementation helps in enforcing constraints on progress values, ensuring they remain valid.
105. What is an iterable?
An iterable is an object that can return an iterator using iter().
Lists, tuples, strings, dictionaries, sets, ranges, and files are common iterables.
106. What is an iterator?
An iterator is an object with __iter__() and __next__().
It remembers where it is during iteration.
107. What is a generator?
A generator is a function that uses yield to produce values lazily.
def count_up_to(limit):
number = 1
while number <= limit:
yield number
number += 1Explanation
- The function
count_up_totakes a single parameterlimit, which defines the maximum number to generate. - It initializes a variable
numberto 1, which serves as the starting point for counting. - A
whileloop is used to iterate as long asnumberis less than or equal tolimit. - The
yieldstatement allows the function to return the current value ofnumberand pause its state, enabling it to resume on the next call. - This approach is memory efficient, as it generates numbers on-the-fly rather than storing them in a list.
108. What is the difference between yield and return?
yield pauses a generator and sends one value.
return ends a normal function, or ends a generator if used inside one.
109. What is lazy evaluation?
Lazy evaluation means values are produced only when needed.
Generators are useful when data is large or infinite.
110. What is a module?
A module is a Python file that can contain variables, functions, classes, and executable code.
It can be imported by another Python file.
111. What is the purpose of if __name__ == "__main__"?
It lets a file run certain code only when executed directly, not when imported.
def main():
print("Run the program")
if __name__ == "__main__": # direct run only
main()Explanation
- The
mainfunction contains a single print statement that outputs "Run the program" to the console. - The
if __name__ == "__main__":condition checks if the script is being executed as the main program, ensuring thatmain()is called only in that context. - This structure allows the script to be imported as a module in other scripts without executing the
mainfunction automatically. - It promotes better organization and modularity in Python code by separating the execution logic from the function definitions.
112. What happens when you import a module?
Python finds the module, runs its top-level code once, creates a module object, and stores it in the import cache.
Later imports usually reuse the cached module.
113. What are different import styles?
Common styles:
import math
import statistics as stats
from pathlib import PathExplanation
- The
mathmodule provides access to mathematical functions like trigonometry, logarithms, and constants. - The
statisticsmodule, imported asstats, offers functions for statistical calculations such as mean, median, and standard deviation. - The
Pathclass from thepathlibmodule is used for object-oriented file system paths, making file manipulation easier and more intuitive. - This code sets up the environment for performing complex calculations and managing file paths effectively in a Python program.
Prefer clear imports and avoid from module import * in normal application code.
114. What is a package?
A package is a directory of related modules.
Modern Python supports namespace packages, and regular packages commonly contain an __init__.py file.
115. What is PyPI?
PyPI is the Python Package Index, a public repository of third-party Python packages.
Tools such as pip can install packages from it.
116. What is sys.path?
sys.path is the list of locations Python searches when importing modules.
It includes the script directory, environment-specific paths, installed package locations, and paths added by the user or tools.
117. What is dir() useful for?
dir() lists names available on an object or in a namespace.
It is useful for exploration, but documentation is better for serious learning.
118. What is a docstring?
A docstring is a string placed at the start of a module, class, or function to document it.
def calculate_average(values):
"""Return the arithmetic mean of a non-empty list."""
return sum(values) / len(values)Explanation
- The function
calculate_averagetakes a single parametervalues, which is expected to be a non-empty list of numbers. - It uses the built-in
sum()function to calculate the total of all elements in the list. - The total is then divided by the number of elements in the list, obtained using
len(), to compute the average. - The function returns the calculated average as a float.
- It assumes that the input list is non-empty, so no error handling for empty lists is included.
119. What is the Zen of Python?
The Zen of Python is a set of design principles for Python.
You can view it with:
import thisExplanation
- The
import thisstatement imports a module that displays the Zen of Python. - The Zen of Python is a collection of aphorisms that capture the philosophy of Python programming.
- When executed, it prints a list of guiding principles that emphasize readability, simplicity, and explicitness.
- This module is often used to inspire Python developers and remind them of best practices.
In interviews, it is enough to mention readability, simplicity, explicitness, and practical design.
120. What makes a good Python interview answer?
A good answer explains behavior, not only syntax.
For coding questions, mention:
- input assumptions
- edge cases
- time complexity
- space complexity
- why the chosen data structure fits
Part 2: Output Prediction Questions
Use these to practice tracing Python behavior.
Output 1: Aliasing
first = [1, 2]
second = first
third = first.copy()
second.append(3)
third.append(4)
print(first)
print(third)Explanation
- The variable
firstis initialized as a list containing the elements 1 and 2. - The variable
secondis assigned to reference the same list asfirst, meaning changes tosecondwill affectfirst. - The variable
thirdis created as a copy offirst, so it is a separate list that initially contains the same elements. - When
3is appended tosecond, it modifies the original listfirst, resulting infirstbeing[1, 2, 3]. - Appending
4tothirddoes not affectfirst, sothirdremains[1, 2, 4], demonstrating the difference between referencing and copying lists.
Answer:
[1, 2, 3]
[1, 2, 4]Output 2: Truthy and Falsy
values = [0, "", [], "python"]
for value in values:
print(bool(value))Explanation
- A list named
valuesis created containing different data types: an integer, a string, an empty list, and a non-empty string. - The
forloop iterates over each item in thevalueslist. - The
bool()function is called on eachvalue, converting it to its boolean equivalent based on Python's truthiness rules. - The results of the boolean evaluations are printed to the console, showing
Falsefor falsy values (0, "", []) andTruefor truthy values ("python"). - This snippet illustrates how Python treats various data types in conditional contexts.
Answer:
False
False
False
TrueOutput 3: Loop Else
numbers = [2, 4, 6]
for number in numbers:
if number % 2 == 1:
print("odd found")
break
else:
print("all even")Explanation
- A list named
numbersis initialized with three even integers: 2, 4, and 6. - A
forloop iterates through each number in thenumberslist. - Inside the loop, an
ifstatement checks if the current number is odd by using the modulus operator (%). - If an odd number is found, it prints "odd found" and exits the loop using
break. - If the loop completes without finding any odd numbers, the
elseblock executes, printing "all even".
Answer:
all evenOutput 4: Default Argument Trap
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
print(add_item("a"))
print(add_item("b"))Explanation
- The function
add_itemtakes anitemand an optionalbucketlist, which defaults to an empty list if not provided. - When an item is added to
bucket, it modifies the same list object across multiple function calls due to the mutable nature of lists. - The first call
add_item("a")appends "a" to the default list, while the second calladd_item("b")appends "b" to the same list, resulting in["a", "b"]. - This behavior can lead to unexpected results if the function is called multiple times, as the default list retains its state between calls.
- To avoid this issue, it's recommended to use
Noneas a default value and initialize the list inside the function.
Answer:
['a']
['a', 'b']The same default list is reused.
Output 5: Dictionary Key Replacement
data = {"a": 1, "b": 2, "a": 3}
print(data)Explanation
- A dictionary in Python is a collection of key-value pairs where each key must be unique.
- In the provided code, the key "a" is defined twice with different values (1 and 3).
- When the dictionary is created, the second definition of "a" (value 3) overwrites the first (value 1).
- The output of
print(data)will show{'a': 3, 'b': 2}, reflecting the final state of the dictionary. - This behavior highlights the importance of unique keys in dictionaries to avoid unintentional data loss.
Answer:
{'a': 3, 'b': 2}The later value replaces the earlier value for the same key.
Output 6: Set Uniqueness
items = {1, True, 2, False, 0}
print(len(items))Explanation
- A set named
itemsis created containing five elements: integers and boolean values. - Sets in Python automatically remove duplicate values, so
Trueand1are considered the same, as well asFalseand0. - The
len()function is used to calculate the number of unique elements in the set. - The output of
print(len(items))will be3, reflecting the unique values:{1, 2, False}.
Answer:
31 and True compare equal. 0 and False compare equal.
Output 7: Closure
def make_adder(amount):
def add(number):
return number + amount
return add
add_five = make_adder(5)
print(add_five(10))Explanation
- The
make_adderfunction takes a parameteramountand defines an inner functionaddthat adds thisamountto a givennumber. - The inner function
addcaptures theamountvariable from its enclosing scope, creating a closure. - When
make_adder(5)is called, it returns a new functionadd_fivethat adds 5 to its input. - The
print(add_five(10))statement outputs 15, as it adds 5 to the input value of 10.
Answer:
15Output 8: Generator State
def numbers():
yield 1
yield 2
yield 3
stream = numbers()
print(next(stream))
print(next(stream))Explanation
- The
numbersfunction is defined as a generator using theyieldkeyword, which allows it to produce a sequence of values one at a time. - When
numbers()is called, it returns a generator object that can be iterated over. - The
next()function is used to retrieve the next value from the generator, which in this case will output1on the first call and2on the second call. - The generator maintains its state between calls, allowing it to continue yielding values from where it left off.
Answer:
1
2Output 9: Inheritance
class Base:
def message(self):
return "base"
class Child(Base):
def message(self):
return "child"
item = Child()
print(item.message())Explanation
- Defines a base class
Basewith a methodmessagethat returns the string "base". - Creates a subclass
Childthat overrides themessagemethod to return the string "child". - Instantiates an object
itemof theChildclass. - Calls the
messagemethod on theitemobject, which executes the overridden method in theChildclass, printing "child".
Answer:
childOutput 10: Exception Flow
try:
number = int("10")
except ValueError:
print("bad")
else:
print("good")
finally:
print("done")Explanation
- The
tryblock attempts to convert the string "10" into an integer. - If a
ValueErroroccurs during the conversion, theexceptblock executes, printing "bad". - If the conversion is successful, the
elseblock runs, printing "good". - The
finallyblock executes regardless of whether an exception occurred, printing "done". - This structure ensures that cleanup or final actions are performed after the try-except logic.
Answer:
good
doneOutput 11: List Comprehension Scope
numbers = [1, 2, 3]
squares = [number * number for number in numbers]
print(squares)Explanation
- A list called
numbersis initialized with three integer values: 1, 2, and 3. - A list comprehension is used to generate a new list called
squares, where each element is the square of the corresponding element innumbers. - The expression
number * numbercalculates the square of each number during the iteration. - Finally, the
printfunction outputs thesquareslist, which contains the squared values [1, 4, 9].
Answer:
[1, 4, 9]Output 12: Slicing
text = "abcdef"
print(text[1:5:2])
print(text[::-1])Explanation
- The first print statement
print(text[1:5:2])slices the stringtextfrom index 1 to 5, taking every 2nd character, resulting in "bd". - The second print statement
print(text[::-1])reverses the stringtextby using a step of -1, producing "fedcba". - String slicing allows for flexible extraction of substrings and manipulation of string data in Python.
- The syntax
text[start:end:step]is a powerful feature for accessing specific parts of a string efficiently.
Answer:
bd
fedcbaOutput 13: Class Variable
class Counter:
total = 0
def __init__(self):
Counter.total += 1
Counter()
Counter()
print(Counter.total)Explanation
- The
Counterclass has a class variabletotalinitialized to 0, which keeps track of the number of instances. - The
__init__method increments thetotalvariable by 1 each time a new instance ofCounteris created. - Two instances of
Counterare created, triggering the__init__method twice, thus increasing thetotalto 2. - Finally, the current value of
Counter.totalis printed, which outputs the total number of instances created.
Answer:
2Output 14: Function Name Preservation
import functools
def keep_name(function_to_call):
@functools.wraps(function_to_call)
def wrapper():
return function_to_call()
return wrapper
@keep_name
def load_page():
return "ok"
print(load_page.__name__)Explanation
- The
keep_namefunction is a decorator that takes another function as an argument and wraps it in a new function calledwrapper. - The
functools.wrapsdecorator is used to ensure that the metadata of the original function (like its name) is preserved in the wrapper function. - The
load_pagefunction is decorated with@keep_name, which means its original name will be maintained when accessed. - When
print(load_page.__name__)is executed, it outputs "load_page" instead of "wrapper", demonstrating the effect of the decorator.
Answer:
load_pageOutput 15: Dictionary View
scores = {"a": 1}
keys = scores.keys()
scores["b"] = 2
print(list(keys))Explanation
- A dictionary
scoresis initialized with a single key-value pair:"a": 1. - The
keys()method is called on thescoresdictionary, creating a view objectkeysthat reflects the current keys in the dictionary. - A new key-value pair
"b": 2is added to thescoresdictionary after obtaining the keys view. - The
printfunction outputs the list of keys, which now includes both"a"and"b"due to the dynamic nature of the keys view.
Answer:
['a', 'b']Dictionary views are dynamic.
Solution Key
Practice Solutions: Coding Interview Problems
Each problem uses fresh examples and avoids course-specific problem statements.
Problem 1: Count Value Types
Write a function that receives a list of values and returns how many are positive, negative, and zero.
def count_number_types(numbers):
result = {"positive": 0, "negative": 0, "zero": 0}
for number in numbers:
if number > 0:
result["positive"] += 1
elif number < 0:
result["negative"] += 1
else:
result["zero"] += 1
return result
assert count_number_types([3, -1, 0, 9, -8]) == {
"positive": 2,
"negative": 2,
"zero": 1,
}Explanation
- The function
count_number_typesinitializes a dictionary to store counts of positive, negative, and zero values. - It iterates through each number in the provided list, updating the respective count based on the number's value.
- The function returns the dictionary containing the counts after processing all numbers.
- An assertion checks that the function correctly counts the types in a sample list, ensuring its accuracy.
Time complexity: O(n).
Space complexity: O(1).
Problem 2: Remove Duplicate Values While Preserving Order
def unique_in_order(values):
seen = set()
result = []
for value in values:
if value not in seen:
seen.add(value)
result.append(value)
return result
assert unique_in_order(["py", "sql", "py", "git"]) == ["py", "sql", "git"]Explanation
- The function
unique_in_ordertakes a listvaluesas input. - It initializes an empty set
seento track unique values and an empty listresultto store the filtered values. - It iterates through each
valuein the input list, checking if it has already been encountered. - If a
valueis not inseen, it adds the value to bothseenandresult, ensuring duplicates are excluded. - Finally, the function returns the
resultlist containing only unique values in their original order.
Time complexity: O(n) average.
Space complexity: O(n).
Problem 3: First Repeated Item
def first_repeated(values):
seen = set()
for value in values:
if value in seen:
return value
seen.add(value)
return None
assert first_repeated([4, 7, 2, 7, 4]) == 7
assert first_repeated([1, 2, 3]) is NoneExplanation
- The function
first_repeatedtakes a list of values as input and initializes an empty set calledseento track unique elements. - It iterates through each
valuein the input list, checking if thevalueis already present in theseenset. - If a
valueis found inseen, it is returned immediately as the first repeated element. - If no repeated elements are found during the iteration, the function returns
None. - The assertions at the end test the function with two cases: one with a repeated element and one without any repetitions.
Problem 4: Most Frequent Word
def most_frequent_word(text):
counts = {}
for raw_word in text.lower().split():
word = raw_word.strip(".,!?")
counts[word] = counts.get(word, 0) + 1
best_word = None
best_count = 0
for word, count in counts.items():
if count > best_count:
best_word = word
best_count = count
return best_word, best_count
assert most_frequent_word("Python is fun. Python is clear.") == ("python", 2)Explanation
- The function
most_frequent_wordtakes a string inputtextand counts the occurrences of each word. - It converts the text to lowercase and splits it into individual words, stripping punctuation marks for accurate counting.
- A dictionary
countsis used to store each word as a key and its count as the value. - The function iterates through the dictionary to find the word with the highest count, storing it in
best_wordalong with its count inbest_count. - Finally, it returns the most frequent word and its count, as demonstrated by the assertion test.
Problem 5: Rotate a List Left by k Positions
def rotate_left(values, steps):
if not values:
return []
steps = steps % len(values)
return values[steps:] + values[:steps]
assert rotate_left([1, 2, 3, 4, 5], 2) == [3, 4, 5, 1, 2]Explanation
- The function
rotate_lefttakes a listvaluesand an integerstepsas input. - It first checks if the list is empty; if so, it returns an empty list.
- The number of steps is adjusted using modulo operation to handle cases where steps exceed the list length.
- The list is then sliced into two parts: from
stepsto the end and from the start tosteps, and these parts are concatenated to achieve the rotation. - An assertion tests the function to ensure it correctly rotates the list
[1, 2, 3, 4, 5]by 2 steps, resulting in[3, 4, 5, 1, 2].
Problem 6: Pair Sum Exists
def has_pair_sum(numbers, target):
seen = set()
for number in numbers:
needed = target - number
if needed in seen:
return True
seen.add(number)
return False
assert has_pair_sum([8, 3, 5, 2], 7) is True
assert has_pair_sum([8, 3, 5, 2], 20) is FalseExplanation
- The function
has_pair_sumtakes a list of integersnumbersand an integertargetas inputs. - It initializes an empty set
seento keep track of numbers encountered during iteration. - For each number in the list, it calculates the
neededvalue that, when added to the current number, equals thetarget. - If the
neededvalue is found in theseenset, the function returnsTrue, indicating a valid pair exists. - If no such pair is found after checking all numbers, the function returns
False.
Time complexity: O(n).
Problem 7: Return Pair Sum Indices
def pair_sum_indices(numbers, target):
positions = {}
for index, number in enumerate(numbers):
needed = target - number
if needed in positions:
return positions[needed], index
positions[number] = index
return None
assert pair_sum_indices([10, 4, 6, 8], 14) == (0, 1)Explanation
- The function
pair_sum_indicestakes a list of numbers and a target sum as input. - It uses a dictionary
positionsto store the indices of numbers encountered during iteration. - For each number, it calculates the difference needed to reach the target and checks if this difference exists in the dictionary.
- If a match is found, it returns the indices of the two numbers that sum to the target.
- If no such pair exists, the function returns
None.
Problem 8: Merge Two Score Dictionaries
If a key appears in both dictionaries, add the values.
def merge_scores(first, second):
merged = first.copy()
for name, score in second.items():
merged[name] = merged.get(name, 0) + score
return merged
assert merge_scores({"a": 2, "b": 1}, {"a": 3, "c": 5}) == {
"a": 5,
"b": 1,
"c": 5,
}Explanation
- The function
merge_scorestakes two dictionaries,firstandsecond, as input parameters. - It creates a copy of the first dictionary to avoid modifying the original data.
- It iterates through each key-value pair in the second dictionary, adding the score to the corresponding key in the merged dictionary.
- If a key from the second dictionary does not exist in the first, it initializes the score to zero before adding.
- The function returns the merged dictionary, which contains the combined scores from both input dictionaries.
Problem 9: Group Items by First Letter
def group_by_first_letter(words):
grouped = {}
for word in words:
if not word:
continue
key = word[0].lower()
grouped.setdefault(key, []).append(word)
return grouped
assert group_by_first_letter(["Python", "patterns", "SQL"]) == {
"p": ["Python", "patterns"],
"s": ["SQL"],
}Explanation
- The function
group_by_first_lettertakes a list of words as input and initializes an empty dictionarygroupedto store the results. - It iterates through each word in the input list, skipping any empty strings.
- For each non-empty word, it converts the first letter to lowercase and uses it as a key in the
groupeddictionary, appending the word to the corresponding list. - The
setdefaultmethod is used to create a new list if the key does not already exist, ensuring that words are grouped correctly. - Finally, the function returns the
groupeddictionary, which contains lists of words organized by their first letters.
Problem 10: Count Character Runs
Return consecutive character counts.
def character_runs(text):
if not text:
return []
result = []
current = text[0]
count = 1
for character in text[1:]:
if character == current:
count += 1
else:
result.append((current, count))
current = character
count = 1
result.append((current, count))
return result
assert character_runs("aaabbc") == [("a", 3), ("b", 2), ("c", 1)]Explanation
- The function
character_runstakes a string input and returns a list of tuples, each containing a character and its consecutive count. - It initializes an empty list
resultto store the character counts and sets the first character ascurrentwith an initial count of 1. - It iterates through the string starting from the second character, comparing each character with
current. If they match, it increments the count; otherwise, it appends the current character and its count toresultand updatescurrentandcount. - After the loop, it appends the last character and its count to
resultbefore returning the final list. - The assertion checks that the function correctly processes the input "aaabbc" to yield the expected output of character counts.
Problem 11: Encode Character Runs
def encode_runs(text):
parts = []
for character, count in character_runs(text):
parts.append(f"{count}{character}")
return "".join(parts)
assert encode_runs("aaabbc") == "3a2b1c"Explanation
- The
encode_runsfunction takes a stringtextas input and initializes an empty listpartsto store encoded segments. - It iterates over the output of the
character_runsfunction, which presumably returns tuples of characters and their consecutive counts. - For each character and its count, it appends a formatted string (count followed by the character) to the
partslist. - Finally, it joins all elements in
partsinto a single string and returns it. - The assertion checks that the function correctly encodes the input "aaabbc" to "3a2b1c".
Problem 12: Check Balanced Brackets
def has_balanced_brackets(text):
pairs = {")": "(", "]": "[", "}": "{"}
openings = set(pairs.values())
stack = []
for character in text:
if character in openings:
stack.append(character)
elif character in pairs:
if not stack or stack[-1] != pairs[character]:
return False
stack.pop()
return not stack
assert has_balanced_brackets("{[()]}") is True
assert has_balanced_brackets("{[(])}") is FalseExplanation
- The function
has_balanced_bracketstakes a string input and checks for balanced parentheses, brackets, and braces. - It uses a dictionary
pairsto map closing brackets to their corresponding opening brackets and a setopeningsto store the opening brackets. - A stack is employed to keep track of opening brackets as they are encountered in the string.
- For each character in the input string, if it is an opening bracket, it is pushed onto the stack; if it is a closing bracket, the function checks if it matches the last opening bracket in the stack.
- The function returns
Trueif all brackets are balanced (i.e., the stack is empty at the end) andFalseotherwise, as demonstrated by the assertions.
Problem 13: Matrix Row Maximums
def row_maximums(matrix):
result = []
for row in matrix:
if not row:
result.append(None)
else:
result.append(max(row))
return result
assert row_maximums([[3, 1], [9, 4], []]) == [3, 9, None]Explanation
- The function
row_maximumstakes a 2D list (matrix) as input and initializes an empty listresultto store the maximum values. - It iterates through each
rowin the matrix; if a row is empty, it appendsNoneto the result list. - For non-empty rows, it calculates the maximum value using the
max()function and appends it to the result list. - Finally, the function returns the
resultlist containing the maximum values for each row, includingNonefor any empty rows. - An assertion is included to verify that the function works correctly with a sample input.
Problem 14: Transpose a Matrix
def transpose(matrix):
if not matrix:
return []
column_count = len(matrix[0])
result = []
for column_index in range(column_count):
column = []
for row in matrix:
column.append(row[column_index])
result.append(column)
return result
assert transpose([[1, 2, 3], [4, 5, 6]]) == [[1, 4], [2, 5], [3, 6]]Explanation
- The function
transposetakes a 2D list (matrix) as input and returns its transposed version. - It first checks if the input matrix is empty; if so, it returns an empty list.
- The number of columns in the original matrix is determined using
len(matrix[0]). - A nested loop iterates through each column index and each row to build the transposed columns.
- Finally, the transposed matrix is returned, and an assertion checks the correctness of the output.
Problem 15: Flatten a Nested List
def flatten(values):
result = []
for value in values:
if isinstance(value, list):
result.extend(flatten(value))
else:
result.append(value)
return result
assert flatten([1, [2, [3, 4]], 5]) == [1, 2, 3, 4, 5]Explanation
- The
flattenfunction takes a listvaluesas input and initializes an empty listresultto store flattened values. - It iterates through each item in
values, checking if the item is a list usingisinstance. - If an item is a list, the function calls itself recursively to flatten that sublist and extends
resultwith the returned values. - If the item is not a list, it appends the item directly to
result. - The function ultimately returns a single flattened list, as demonstrated by the assertion that checks the output against the expected result.
Problem 16: Recursive Sum
def recursive_sum(numbers):
if not numbers:
return 0
return numbers[0] + recursive_sum(numbers[1:])
assert recursive_sum([1, 2, 3, 4]) == 10Explanation
- The function
recursive_sumtakes a list of numbers as input. - It checks if the list is empty; if so, it returns 0, which serves as the base case for recursion.
- If the list is not empty, it adds the first element of the list to the result of a recursive call with the rest of the list.
- The assertion tests the function with a sample list
[1, 2, 3, 4], confirming that the output is 10, which is the correct sum.
This version is easy to understand, but slicing creates extra lists. For large lists, an index-based version is better.
Problem 17: Index-Based Recursive Sum
def recursive_sum_from(numbers, index=0):
if index == len(numbers):
return 0
return numbers[index] + recursive_sum_from(numbers, index + 1)
assert recursive_sum_from([1, 2, 3, 4]) == 10Explanation
- The function
recursive_sum_fromtakes a listnumbersand an optionalindexparameter, defaulting to 0. - It checks if the current
indexis equal to the length of the list; if so, it returns 0, which serves as the base case for recursion. - If not at the end of the list, it adds the current number at
indexto the result of a recursive call with the next index (index + 1). - The assertion tests the function with a list
[1, 2, 3, 4], confirming that the sum calculated is 10.
Problem 18: Binary Search
def binary_search(sorted_values, target):
left = 0
right = len(sorted_values) - 1
while left <= right:
middle = (left + right) // 2
if sorted_values[middle] == target:
return middle
if sorted_values[middle] < target:
left = middle + 1
else:
right = middle - 1
return -1
assert binary_search([2, 5, 8, 12], 8) == 2
assert binary_search([2, 5, 8, 12], 7) == -1Explanation
- Defines a function
binary_searchthat takes a sorted list and a target value as inputs. - Initializes two pointers,
leftandright, to represent the current search boundaries. - Uses a while loop to repeatedly narrow down the search range until the target is found or the range is exhausted.
- Calculates the middle index and compares the middle value with the target to determine the next search direction.
- Returns the index of the target if found, or -1 if the target is not present in the list.
Time complexity: O(log n).
Problem 19: Maximum Subarray Sum
def maximum_subarray_sum(numbers):
if not numbers:
raise ValueError("numbers must not be empty")
best = numbers[0]
current = numbers[0]
for number in numbers[1:]:
current = max(number, current + number)
best = max(best, current)
return best
assert maximum_subarray_sum([-2, 4, -1, 3, -5]) == 6Explanation
- The function
maximum_subarray_sumtakes a list of integers as input and raises an error if the list is empty. - It initializes two variables,
bestandcurrent, to the first element of the list to track the maximum sum found so far and the current subarray sum. - The function iterates through the list starting from the second element, updating
currentto be either the current number or the sum ofcurrentand the current number, whichever is larger. - It updates
bestwhenevercurrentexceeds its value, ensuring it always holds the maximum contiguous subarray sum. - The final result is returned, and an assertion checks that the function correctly computes the maximum subarray sum for a given test case.
Problem 20: Product Except Current Index
def product_except_current(numbers):
result = [1] * len(numbers)
prefix = 1
for index, number in enumerate(numbers):
result[index] = prefix
prefix *= number
suffix = 1
for index in range(len(numbers) - 1, -1, -1):
result[index] *= suffix
suffix *= numbers[index]
return result
assert product_except_current([2, 3, 4]) == [12, 8, 6]Explanation
- The function
product_except_currenttakes a list of numbers and returns a new list where each element is the product of all other elements in the input list, excluding the current element. - It initializes a result list filled with ones, which will store the final products.
- The first loop calculates the prefix products for each element, storing the cumulative product of all previous elements in the result list.
- The second loop calculates the suffix products in reverse order, multiplying the current result by the cumulative product of all subsequent elements.
- The final output is a list where each index contains the product of all numbers in the input list except the number at that index, validated by the assertion statement.
Problem 21: Longest Consecutive Streak
def longest_consecutive_streak(numbers):
values = set(numbers)
best = 0
for number in values:
if number - 1 in values:
continue
current = number
length = 1
while current + 1 in values:
current += 1
length += 1
best = max(best, length)
return best
assert longest_consecutive_streak([10, 4, 20, 1, 3, 2]) == 4Explanation
- The function
longest_consecutive_streaktakes a list of integers as input and identifies the longest sequence of consecutive numbers. - It converts the input list into a set to enable O(1) average time complexity for membership checks.
- The outer loop iterates through each unique number, skipping those that are not the start of a sequence (i.e., if the previous number exists in the set).
- An inner while loop counts the length of the consecutive sequence starting from the current number.
- Finally, it returns the maximum length found among all sequences, as demonstrated by the assertion test.
Problem 22: Longest Substring Without Repeating Characters
def longest_unique_substring_length(text):
last_seen = {}
start = 0
best = 0
for index, character in enumerate(text):
if character in last_seen and last_seen[character] >= start:
start = last_seen[character] + 1
last_seen[character] = index
best = max(best, index - start + 1)
return best
assert longest_unique_substring_length("abcaef") == 5Explanation
- The function
longest_unique_substring_lengthtakes a stringtextas input and initializes a dictionarylast_seento track the last index of each character. - It uses two variables,
startto mark the beginning of the current substring andbestto store the maximum length found. - The function iterates through each character in the string, checking if it has been seen before and is within the current substring range.
- If a duplicate character is found,
startis updated to the index right after the last occurrence of that character. - Finally, it calculates the length of the current substring and updates
bestif this length is greater, returning the maximum length of unique substrings.
Problem 23: Safe Average
def safe_average(values):
if not values:
raise ValueError("values must not be empty")
total = 0
for value in values:
if not isinstance(value, (int, float)):
raise TypeError("all values must be numeric")
total += value
return total / len(values)
assert safe_average([10, 20, 30]) == 20Explanation
- The function
safe_averagetakes a list of values and checks if it is empty, raising aValueErrorif so. - It initializes a
totalvariable to accumulate the sum of numeric values. - The function iterates through each value, checking if it is either an integer or a float, raising a
TypeErrorfor any non-numeric values. - Finally, it returns the average by dividing the total by the number of values in the list.
- An assertion is included to verify that the function correctly computes the average of the provided list.
Problem 24: Parse Key-Value Lines
def parse_key_value_lines(lines):
result = {}
for line in lines:
if "=" not in line:
continue
key, value = line.split("=", 1)
result[key.strip()] = value.strip()
return result
assert parse_key_value_lines(["name = Ana", "score=91", "bad line"]) == {
"name": "Ana",
"score": "91",
}Explanation
- The function
parse_key_value_linestakes a list of strings as input, where each string is expected to contain a key-value pair separated by an equals sign (=). - It initializes an empty dictionary
resultto store the parsed key-value pairs. - The function iterates through each line in the input list, skipping any line that does not contain an equals sign.
- For valid lines, it splits the line into a key and a value at the first equals sign, trims any whitespace, and adds them to the
resultdictionary. - Finally, it returns the populated dictionary containing all valid key-value pairs.
Problem 25: Serialize Progress to JSON
import json
def progress_to_json(username, completed_lessons):
payload = {
"username": username,
"completed_lessons": completed_lessons,
}
return json.dumps(payload, sort_keys=True)
assert progress_to_json("dev", 4) == '{"completed_lessons": 4, "username": "dev"}'Explanation
- The function
progress_to_jsontakes two parameters:usernameandcompleted_lessons. - It creates a dictionary
payloadcontaining the user's name and the number of lessons they have completed. - The
json.dumpsmethod is used to convert the dictionary into a JSON string, with keys sorted for consistency. - An assertion checks that the function correctly formats the output for a specific input, ensuring the function behaves as expected.
Problem 26: Chunk Values With a Generator
def chunked(values, size):
if size <= 0:
raise ValueError("size must be positive")
for index in range(0, len(values), size):
yield values[index:index + size]
assert list(chunked([1, 2, 3, 4, 5], 2)) == [[1, 2], [3, 4], [5]]Explanation
- The
chunkedfunction takes a listvaluesand an integersizeas input parameters. - It raises a
ValueErrorif thesizeis less than or equal to zero, ensuring valid input. - The function uses a
forloop with a step ofsizeto iterate through the list, yielding sublists of the specified size. - The
yieldstatement allows the function to return chunks one at a time, making it memory efficient. - The assertion tests the function by checking if the output matches the expected list of chunks when the input list is divided into groups of two.
Problem 27: Create a Countdown Iterator
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
assert list(Countdown(3)) == [3, 2, 1]Explanation
- The
Countdownclass initializes with a starting number and maintains the current countdown value. - The
__iter__method returns the iterator object itself, allowing it to be used in a loop. - The
__next__method decrements the current value and returns it until it reaches zero, at which point it raises aStopIterationexception to signal the end of the iteration. - The assertion at the end verifies that creating a list from the
Countdowniterator with a starting value of 3 produces the expected countdown list[3, 2, 1].
Problem 28: Decorator That Requires a Key
import functools
def require_key(key):
def decorator(function_to_call):
@functools.wraps(function_to_call)
def wrapper(payload, *args, **kwargs):
if key not in payload:
raise KeyError(f"missing key: {key}")
return function_to_call(payload, *args, **kwargs)
return wrapper
return decorator
@require_key("title")
def publish_payload(payload):
return payload["title"].strip().title()
assert publish_payload({"title": "python basics"}) == "Python Basics"Explanation
- The
require_keyfunction is a decorator factory that checks for the presence of a specified key in a given payload. - It defines an inner
wrapperfunction that raises aKeyErrorif the required key is missing from the payload. - The decorator is applied to the
publish_payloadfunction, which processes the payload by extracting and formatting the value associated with the "title" key. - The assertion at the end verifies that the
publish_payloadfunction correctly formats the title from the provided payload.
Problem 29: Class for Tracking Scores
class ScoreTracker:
def __init__(self):
self._scores = []
def add(self, score):
if score < 0:
raise ValueError("score must not be negative")
self._scores.append(score)
@property
def average(self):
if not self._scores:
return 0
return sum(self._scores) / len(self._scores)
tracker = ScoreTracker()
tracker.add(80)
tracker.add(100)
assert tracker.average == 90Explanation
- The
ScoreTrackerclass initializes with an empty list to store scores. - The
addmethod allows adding a score, raising aValueErrorif the score is negative. - The
averageproperty computes the average of the stored scores, returning 0 if no scores are present. - An instance of
ScoreTrackeris created, and two scores (80 and 100) are added. - The average score is asserted to be 90, confirming the correct functionality of the class.
Problem 30: Polymorphic Exporters
class TextExporter:
def export(self, rows):
return "\n".join(rows)
class CsvExporter:
def export(self, rows):
return ",".join(rows)
def export_rows(exporter, rows):
return exporter.export(rows)
assert export_rows(TextExporter(), ["a", "b"]) == "a\nb"
assert export_rows(CsvExporter(), ["a", "b"]) == "a,b"Explanation
- Defines two classes,
TextExporterandCsvExporter, each implementing anexportmethod to format data differently. - The
export_rowsfunction takes an exporter object and a list of rows, calling the appropriate export method based on the provided exporter. - The assertions at the end verify that the
export_rowsfunction correctly formats the input data for both text and CSV formats. - This design allows for easy extension to support additional export formats by creating new exporter classes without modifying existing code.
Problem 31: Abstract Export Contract
import abc
class Exporter(abc.ABC):
@abc.abstractmethod
def export(self, rows):
raise NotImplementedError
class JsonLineExporter(Exporter):
def export(self, rows):
return "\n".join(rows)
exporter = JsonLineExporter()
assert exporter.export(["one", "two"]) == "one\ntwo"Explanation
- The
Exporterclass is defined as an abstract base class using theabcmodule, enforcing the implementation of theexportmethod in subclasses. - The
exportmethod inExporteris marked as abstract, meaning any subclass must provide its own implementation. - The
JsonLineExporterclass inherits fromExporterand implements theexportmethod to convert a list of rows into a single string with each row on a new line. - An instance of
JsonLineExporteris created, and theexportmethod is tested with a sample list, asserting that the output matches the expected JSON lines format.
Problem 32: Validate a Password
def is_valid_password(password):
if len(password) < 8:
return False
has_digit = any(character.isdigit() for character in password)
has_alpha = any(character.isalpha() for character in password)
return has_digit and has_alpha
assert is_valid_password("python3x") is True
assert is_valid_password("short1") is FalseExplanation
- The function
is_valid_passwordverifies that the password is at least 8 characters long. - It checks for the presence of at least one digit and one alphabetic character using generator expressions.
- The function returns
Trueif both conditions are satisfied; otherwise, it returnsFalse. - Two assertions test the function: one with a valid password and another with an invalid one.
Problem 33: Convert Snake Case to Title
def snake_to_title(name):
words = name.split("_")
cleaned = [word.capitalize() for word in words if word]
return " ".join(cleaned)
assert snake_to_title("python_interview_guide") == "Python Interview Guide"Explanation
- The function
snake_to_titletakes a stringnameformatted in snake_case as input. - It splits the string into individual words using the underscore
_as a delimiter. - A list comprehension is used to capitalize each word while filtering out any empty strings.
- Finally, the cleaned words are joined together with a space to form a title case string.
- An assertion checks that the function correctly transforms "python_interview_guide" into "Python Interview Guide".
Problem 34: Find Missing Number From 1 to n
def missing_number(values, limit):
expected = limit * (limit + 1) // 2
actual = sum(values)
return expected - actual
assert missing_number([1, 2, 4, 5], 5) == 3Explanation
- The function
missing_numbertakes a list of integersvaluesand an integerlimitas input. - It calculates the expected sum of the first
limitnatural numbers using the formulalimit * (limit + 1) // 2. - It computes the actual sum of the numbers present in the
valueslist. - The function returns the difference between the expected sum and the actual sum, which represents the missing number.
- An assertion checks that the function correctly identifies the missing number (3) when given the input list
[1, 2, 4, 5]and a limit of 5.
Problem 35: Find Second Largest Distinct Value
def second_largest_distinct(numbers):
first = None
second = None
for number in numbers:
if number == first or number == second:
continue
if first is None or number > first:
second = first
first = number
elif second is None or number > second:
second = number
if second is None:
raise ValueError("need at least two distinct values")
return second
assert second_largest_distinct([5, 1, 5, 3]) == 3Explanation
- Defines a function
second_largest_distinctthat takes a list of numbers as input. - Initializes two variables,
firstandsecond, to track the largest and second largest distinct numbers. - Iterates through each number in the list, skipping duplicates and updating
firstandsecondas necessary. - Raises a
ValueErrorif there are not at least two distinct values in the input list. - The function returns the second largest distinct number, as demonstrated by the assertion test.
Problem 36: Build a Small Command Router
def start():
return "starting"
def stop():
return "stopping"
def unknown():
return "unknown command"
def run_command(command):
actions = {
"start": start,
"stop": stop,
}
action = actions.get(command, unknown)
return action()
assert run_command("start") == "starting"
assert run_command("pause") == "unknown command"Explanation
- The
startandstopfunctions return strings indicating their respective actions. - The
unknownfunction returns a default message for unrecognized commands. - The
run_commandfunction maps command strings to their corresponding functions using a dictionary. - If a command is not found in the dictionary, it defaults to the
unknownfunction. - The assertions at the end verify that the
run_commandfunction behaves as expected for both valid and invalid commands.
Problem 37: Sort Records by Multiple Fields
def sort_learners(records):
return sorted(records, key=lambda item: (-item["score"], item["name"]))
learners = [
{"name": "Bea", "score": 90},
{"name": "Ana", "score": 90},
{"name": "Dev", "score": 80},
]
assert sort_learners(learners) == [
{"name": "Ana", "score": 90},
{"name": "Bea", "score": 90},
{"name": "Dev", "score": 80},
]Explanation
- The
sort_learnersfunction takes a list of dictionaries, each representing a learner with a name and score. - It uses the
sorted()function with a custom sorting key defined by a lambda function. - The sorting key prioritizes the score in descending order (using
-item["score"]) and then the name in ascending order. - The function returns a new list of learners sorted according to the specified criteria.
- An assertion checks that the output matches the expected sorted order for verification.
Problem 38: Implement a Simple Cache Decorator
import functools
def cache_by_argument(function_to_call):
cache = {}
@functools.wraps(function_to_call)
def wrapper(argument):
if argument not in cache:
cache[argument] = function_to_call(argument)
return cache[argument]
return wrapper
@cache_by_argument
def square(number):
return number * number
assert square(6) == 36Explanation
- The
cache_by_argumentfunction is a decorator that caches the results of a function based on its input arguments to avoid redundant calculations. - A dictionary named
cacheis used to store results, where the keys are the input arguments and the values are the corresponding outputs of the function. - The
wrapperfunction checks if the argument is already in the cache; if not, it computes the result and stores it in the cache. - The
@functools.wrapsdecorator is applied to preserve the original function's metadata, such as its name and docstring. - The
squarefunction, decorated with@cache_by_argument, calculates the square of a number and benefits from caching for improved performance on repeated calls with the same argument.
Problem 39: Readable File Extension Summary
from pathlib import Path
def count_extensions(paths):
counts = {}
for raw_path in paths:
suffix = Path(raw_path).suffix.lower() or "<none>"
counts[suffix] = counts.get(suffix, 0) + 1
return counts
assert count_extensions(["a.py", "b.PY", "notes"]) == {".py": 2, "<none>": 1}Explanation
- The function
count_extensionstakes a list of file paths as input and initializes an empty dictionarycountsto store the frequency of each file extension. - It iterates through each path, using
Path(raw_path).suffix.lower()to extract the file extension in lowercase, defaulting to "<none>" if there is no extension. - The dictionary is updated with the count of each extension, incrementing the count for existing extensions or initializing it to 1 if it's the first occurrence.
- Finally, the function returns the dictionary containing the counts of each file extension.
- An assertion tests the function to ensure it correctly counts the extensions for the provided list of file paths.
Problem 40: Build a Tiny Module-Style Main Function
def build_message(name):
return f"Hello, {name}"
def main():
print(build_message("Python"))
if __name__ == "__main__": # direct run only
main()Explanation
- The
build_messagefunction takes a single parameter,name, and returns a formatted greeting string. - The
mainfunction callsbuild_messagewith the argument "Python" and prints the resulting message. - The conditional
if __name__ == "__main__":ensures thatmainis executed only when the script is run directly, not when imported as a module. - This structure promotes modularity and reusability of the
build_messagefunction in other contexts.
In an interview, explain that main() runs only when the file is executed directly.
Practice Lab
Practice Exercises
Try these without looking for solutions first.
- Write a function that returns the number of vowels in a string.
- Write a function that reverses words in a sentence but keeps each word's letters unchanged.
- Write a function that checks whether two strings are anagrams.
- Write a function that returns the first non-repeating character in a string.
- Write a function that removes all spaces from a string without using
replace(). - Write a function that capitalizes each word without using
title(). - Write a function that finds the longest word in a sentence.
- Write a function that counts words case-insensitively.
- Write a function that returns common values between two lists without duplicates.
- Write a function that returns values present in the first list but not in the second.
- Write a function that merges two sorted lists.
- Write a function that checks whether a list is sorted in non-decreasing order.
- Write a function that moves all zero values to the end of a list.
- Write a function that returns leaders in a list, where a leader is greater than all values to its right.
- Write a function that returns the kth smallest distinct value.
- Write a function that finds the longest run of the same value in a list.
- Write a function that groups records by a selected dictionary key.
- Write a function that inverts a dictionary whose values are unique.
- Write a function that creates a frequency table from a list.
- Write a function that sorts dictionary keys by their values.
- Write a recursive function that counts digits in a positive integer.
- Write a recursive function that checks whether a string is a palindrome.
- Write a recursive function that finds the maximum value in a list.
- Write a recursive function that counts nested list depth.
- Write a generator that yields even numbers up to a limit.
- Write a generator that yields running totals from a list.
- Write an iterator class that loops over pages of records.
- Write a decorator that measures call count.
- Write a decorator that rejects empty string arguments.
- Write a closure that remembers the last value passed to it.
- Write a class for a bank wallet with deposit, withdraw, and balance.
- Write a class for a quiz question with answer checking.
- Write a class hierarchy for free and premium lessons.
- Write an abstract base class for different report renderers.
- Write a function that writes a dictionary to a JSON file.
- Write a function that reads a JSON file and handles invalid JSON.
- Write a function that reads a text file and returns the top five words.
- Write a function that validates a list of email-like strings.
- Write a command-line menu loop with safe input handling.
- Write a small package layout with one module for calculations and one module for display.
Part 5: Rapid Revision Tables
Data Structure Choice
| Need | Best fit |
|---|---|
| Ordered editable sequence | list |
| Fixed record | tuple |
| Unique values | set |
| Key-value lookup | dict |
| Lazy sequence | generator |
| Reusable object behavior | class |
Common Mistakes
| Mistake | Better habit |
|---|---|
| Using mutable default arguments | Use None and create inside the function |
Catching bare except | Catch specific exceptions |
Using is for value comparison | Use == for values |
Shadowing built-ins like list | Use descriptive names like lesson_list |
| Copying nested lists with shallow copy | Use copy.deepcopy() when nested independence matters |
| Writing code at module top level | Put executable flow inside main() |
| Using a class for everything | Use a function when there is no state or object behavior |
| Ignoring complexity | Explain runtime and memory tradeoffs |
Big O Quick Reference
| Code pattern | Common complexity |
|---|---|
| Direct index lookup in a list | O(1) |
| Loop over one list | O(n) |
| Nested loop over same list | O(n^2) |
| Binary search on sorted data | O(log n) |
| Sorting | O(n log n) |
| Dictionary/set membership average case | O(1) |
Quick Check
Quick Quiz
- What does
input()return? - When should you use
isinstead of==? - Why are lists not valid dictionary keys?
- What does a function return without a
returnstatement? - Why is
list.copy()not enough for nested lists? - What are the two required parts of recursion?
- What is the difference between
yieldandreturn? - Why should decorators use
functools.wraps? - What is the purpose of
if __name__ == "__main__"? - Why should you avoid loading untrusted pickle files?
Quick Check
Quick Quiz Answers
input()returns a string.- Use
isfor identity checks, especiallyis None. - Lists are mutable and unhashable.
- It returns
None. - A shallow copy still shares nested mutable objects.
- A base case and progress toward the base case.
yieldpauses and resumes a generator;returnends a function.- It preserves function metadata such as name and docstring.
- It separates direct script execution from import behavior.
- Pickle can execute unsafe instructions during loading.
Final Interview Checklist
Before an interview, make sure you can:
- explain Python's typing model
- trace list aliasing and copying
- use strings, lists, tuples, sets, and dictionaries confidently
- write functions with clean parameters and return values
- avoid mutable default arguments
- explain recursion with a base case
- estimate time and space complexity
- build small classes with
__init__, methods, and properties - explain inheritance, polymorphism, abstraction, and composition
- handle files safely with context managers
- serialize simple data with JSON
- handle exceptions without hiding bugs
- explain iterables, iterators, and generators
- write a simple decorator with
functools.wraps - explain modules, imports, packages, and
__name__
Sources and Further Reading
- Python tutorial: https://docs.python.org/3/tutorial/
- Python data model: https://docs.python.org/3/reference/datamodel.html
- Python execution model and naming: https://docs.python.org/3/reference/executionmodel.html
- Built-in types: https://docs.python.org/3/library/stdtypes.html
- Built-in functions: https://docs.python.org/3/library/functions.html
- Exceptions: https://docs.python.org/3/library/exceptions.html
json: https://docs.python.org/3/library/json.htmlpickle: https://docs.python.org/3/library/pickle.htmlpathlib: https://docs.python.org/3/library/pathlib.htmlfunctools: https://docs.python.org/3/library/functools.htmlabc: https://docs.python.org/3/library/abc.htmlitertools: https://docs.python.org/3/library/itertools.html- Python packaging user guide: https://packaging.python.org/
