print(5 > 3) # True
print(5 < 3) # False
print(5 == 5) # True
print(5 != 2) # True
print(5 >= 5) # True
age = 20
is_student = True
print(age >= 18 and is_student) # True
print(age < 18 or is_student) # True
print(not is_student) # False
x = 10
x += 5 # x = x + 5
x -= 3 # x = x - 3
x *= 2 # x = x * 2
print(x)
is and is not.
a = [1,2]
b = [1,2]
c = a
print(a is b) # False (different objects)
print(a is c) # True (same object)
print(a is not b) # True
in and not in.
fruits = ["apple", "banana"]
print("apple" in fruits) # True
print("mango" not in fruits) # True
a = 10 # 1010
b = 4 # 0100
print(a & b) # AND
print(a | b) # OR
print(a ^ b) # XOR
print(~a) # NOT
print(a << 1) # Left shift
print(a >> 1) # Right shift
result = 10 + 5 * 2
# multiplication runs first β 10 + 10 = 20
print(result)
x = [1,2]
y = [1,2]
print(x == y) # True
print(x is y) # False
age = 17
status = "Adult" if age >= 18 else "Minor"
print(status)
x = None
if x and x > 5:
print("Greater")
# No error because second condition is skipped
a = 12
b = 4
print(a + b)
print(a - b)
print(a * b)
print(a / b)
A string is an ordered sequence of characters enclosed in quotes.
In Python you can use single (') or double (") quotes.
Interview Tip: Strings are immutable β you cannot change them in-place.
name = "Hari"
greet = 'Hello'
# concatenation
message = greet + " " + name
print(message) # Hello Hari
Strings support zero-based indexing (first char at index 0) and
slicing with the syntax s[start:stop:step].
Negative indices count from the end.
s = "Python"
print(s[0]) # P
print(s[-1]) # n
print(s[1:4]) # yth
print(s[::2]) # Pto (step 2)
Practice: Try to slice "DataScience" to get "Data", "Science", and "Sci".
Immutable means you cannot change a string's characters by index. Any βmodificationβ creates a new string object.
s = "hello"
# β This raises TypeError
# s[0] = "H"
# β
Correct way: create a new string
s2 = "H" + s[1:]
print(s2) # Hello
Interview Tip: Immutability makes strings hashable, so they can be safely used as dictionary keys and set elements.
Common methods: lower(), upper(), strip(),
split(), join(), replace().
s = " Hello, Python "
print(s.strip()) # remove spaces both sides
print(s.lower()) # to lowercase
parts = s.strip().split(",")
print(parts) # ['Hello', ' Python']
words = ["Data", "Science"]
print(" ".join(words)) # Data Science
Practice: Use these methods to clean a comma-separated string of emails.
Palindrome means the string reads the same forwards and backwards. Use normalization (lowercase + remove spaces) and compare with its reverse.
def is_palindrome(s):
clean = s.replace(" ", "").lower()
rev = clean[::-1]
return clean == rev
print(is_palindrome("Madam")) # True
print(is_palindrome("nurses run")) # True
Use a dictionary (or collections.Counter) to store character counts.
s = "banana"
freq = {}
for ch in s:
freq[ch] = freq.get(ch, 0) + 1
print(freq) # {'b':1, 'a':3, 'n':2}
Follow-up: Modify this to count words instead of characters.
To reverse only the order of words while keeping characters inside each word untouched, split the string by spaces, reverse the list, and join it back.
sentence = "Python is super easy"
words = sentence.split()
reversed_words = words[::-1]
result = " ".join(reversed_words)
print(result)
# Output: easy super is Python
Concept: β Split β β Reverse list β β Join β Characters inside each word stay unchanged.
Normalize to lowercase, then check each character against a vowel set.
def count_vowels_consonants(s):
vowels = "aeiou"
v = c = 0
for ch in s.lower():
if ch.isalpha():
if ch in vowels:
v += 1
else:
c += 1
return v, c
print(count_vowels_consonants("Interview"))
isalpha(), isdigit(), and isalnum() with a validation example.
These methods help in input validation:
isalpha() β only letters, isdigit() β only digits,
isalnum() β letters or digits.
username = "hari123"
age = "22"
if username.isalnum() and age.isdigit():
print("Valid input")
else:
print("Invalid")
format()?
f-strings are the modern, readable way introduced in Python 3.6.
format() is older but still common in interviews.
name = "Hari"
score = 95.4567
# f-string
print(f"{name} scored {score:.2f}")
# format()
print("{0} scored {1:.2f}".format(name, score))
Use a set to track seen characters and build a new string. This is a common coding task to test your understanding of strings + sets.
s = "programming"
seen = set()
result = []
for ch in s:
if ch not in seen:
seen.add(ch)
result.append(ch)
print("".join(result)) # progamin
Split the sentence into words and use max() with key=len.
This is a very common string + built-in functions question.
s = "Python interview preparation guide"
words = s.split()
longest = max(words, key=len)
print(longest) # preparation
A list is an ordered, mutable collection used to store multiple items. Mutable means you can add, remove, or update values after creation.
nums = [10, 20, 30]
nums[1] = 25 # list is mutable
print(nums)
nums = [10, 20, 30, 40]
print(nums[2]) # access
nums[1] = 50 # update
del nums[3] # delete
print(nums)
nums = [10, 20, 30, 40, 50]
print(nums[1:4]) # middle slice
print(nums[:3]) # start slice
print(nums[2:]) # end slice
print(nums[::-1]) # reverse list
# normal loop
squares = []
for x in range(5):
squares.append(x*x)
# list comprehension
squares2 = [x*x for x in range(5)]
print(squares2)
nums = [1, 2]
nums.append(3) # add at end
nums.insert(1, 10) # insert at index
nums.extend([20, 30]) # add multiple
print(nums)
nums = [10, 20, 30, 40]
nums.remove(20) # remove by value
nums.pop(1) # remove by index
nums.clear() # delete all items
print(nums)
nums = [5, 2, 9, 1]
print(sorted(nums)) # ascending
print(sorted(nums, reverse=True)) # descending
nums.sort() # in-place
nums.sort(reverse=True)
import copy
a = [[1,2], [3,4]]
b = a.copy() # shallow copy
c = copy.deepcopy(a) # deep copy
a[0][0] = 99
print(b) # changed (shares nested lists)
print(c) # unchanged (full clone)
nums = [10, 20, 10, 30, 20]
unique = []
for n in nums:
if n not in unique:
unique.append(n)
print(unique)
nested = [[1,2], [3,4], [5,6]]
flat = [x for group in nested for x in group]
print(flat)
nums = [10, 50, 30, 40, 50]
unique = list(set(nums))
unique.sort()
print(unique[-2])
names = ["A", "B", "C"]
scores = [90, 85, 88]
pairs = list(zip(names, scores))
print(pairs)
A tuple is an immutable ordered collection. Lists are mutable; tuples are not.
# List vs Tuple
my_list = [1, 2, 3] # mutable
my_tuple = (1, 2, 3) # immutable
# Normal tuple
t1 = (1, 2, 3)
# Without parentheses
t2 = 1, 2, 3
# Single element tuple
t3 = (5,) # comma required
# Using tuple()
t4 = tuple([1, 2, 3])
Tuples use less memory and are stored in compact structures. Python optimizes them internally.
# Faster iteration
my_tuple = (1, 2, 3)
for x in my_tuple:
print(x)
Tuples cannot be modified. But you can create a new tuple from the old one.
t = (1, 2, 3)
t_new = t + (4, 5) # creates a new tuple
print(t_new)
t = (10, 20, 30, 40)
print(t[0]) # 10
print(t[-1]) # 40
print(t[1:3]) # (20, 30)
a, b, c = (1, 2, 3)
print(a, b, c)
# Extended unpacking
x, *y = (5, 6, 7, 8)
print(x) # 5
print(y) # [6, 7, 8]
t = (1, 2, 3, 4)
print(3 in t) # True
print(10 in t) # False
lst = [1, 2, 3]
t = tuple(lst)
t2 = (4, 5, 6)
lst2 = list(t2)
t = (1, [2, 3], 4)
t[1].append(99) # allowed (list is mutable)
print(t) # (1, [2, 3, 99], 4)
A tuple is hashable only if all its elements are immutable.
# Valid key
d = {(1, 2, 3): "OK"}
# Invalid (list is mutable)
# d = {([1,2], 3): "error"}
t = (1, 2, 2, 3, 3, 3)
print(t.count(3)) # 3
print(t.index(2)) # first occurrence
Example: Returning multiple values from a function (safe, immutable).
def get_user():
return ("Hari", 22, "Hyderabad") # tuple result
name, age, city = get_user()
Example:
d["city"] = "Hyderabad"
d = {"name": "Hari"}
d["age"] = 22
d["age"] = 23 # update
print(d)
pop(), del, or popitem().Example:
del d["name"]
d = {"name": "Hari", "age": 22}
d.pop("age")
del d["name"]
print(d)
keys(), values(), and items().Example: Key-value looping with items().
d = {"a": 1, "b": 2}
for k in d.keys():
print(k)
for v in d.values():
print(v)
for k, v in d.items():
print(k, v)
get() avoids KeyError when key is absent.Example:
d.get("x", 0)
d = {"a": 10}
print(d.get("b")) # None
print(d.get("b", 0)) # default value
# print(d["b"]) β KeyError
Example:
{x: x*x for x in range(3)}
squares = {x: x*x for x in range(5)}
print(squares)
| operator or update().Example:
d3 = d1 | d2
d1 = {"a": 1}
d2 = {"b": 2}
print(d1 | d2)
Example:
{"emp": {"name": "Sam"}}
company = {
"emp1": {"name": "Hari", "age": 22},
"emp2": {"name": "Ravi", "age": 30}
}
print(company["emp1"]["name"])
Example: integers, strings, tuples are valid keys.
d = {}
d["name"] = "Hari" # valid
# d[[1, 2]] = "x" # error: unhashable type 'list'
sorted() with key parameter.Example: sort by value β
key=lambda x: x[1]
d = {"b": 3, "a": 1, "c": 2}
print(sorted(d.items())) # by key
print(sorted(d.items(), key=lambda x: x[1])) # by value
Example:
defaultdict(int) β default 0.
from collections import defaultdict
d = defaultdict(int)
d["a"] += 1
print(d) # {'a': 1}
Example:
if x % 2 == 0:
x = 7
if x % 2 == 0:
print("Even")
else:
print("Odd")
elif allows checking multiple conditions in sequence.Example: grading system.
marks = 82
if marks >= 90:
print("A Grade")
elif marks >= 75:
print("B Grade")
else:
print("C Grade")
elif blocks as needed.Example: Temperature classification.
temp = 32
if temp > 40:
print("Hot")
elif temp > 30:
print("Warm")
elif temp > 20:
print("Cool")
else:
print("Cold")
Example: Checking age + ID card.
age = 19
has_id = True
if age >= 18:
if has_id:
print("Entry allowed")
Example:
x = "Even" if n % 2 == 0 else "Odd"
n = 4
result = "Even" if n % 2 == 0 else "Odd"
print(result)
Example:
age >= 18 and country == "IN"
age = 20
country = "IN"
if age >= 18 and country == "IN":
print("Eligible")
Example:
False and heavy_function()
x = 0
if x != 0 and (10 / x) > 1:
print("Won't run")
Example:
3 < x < 10
x = 5
if 3 < x < 10:
print("In range")
Example:
if is_active:
is_active = True
if is_active:
print("Active user")
Example:
if []: is False.
if "":
print("True")
else:
print("False") # empty string is falsy
Example: Standard sign check.
n = int(input("Enter number: "))
if n > 0:
print("Positive")
elif n < 0:
print("Negative")
else:
print("Zero")
Definition: A loop allows repeating a block of code multiple times until a condition is met. It helps automate repetitive tasks efficiently.
| Loop Type | Use Case |
|---|---|
| for loop | Iterating over sequences |
| while loop | Repeat until condition false |
for num in range(3): print(num)
for loop and while loop?Definition: A for loop iterates over sequences, while a while loop runs until a condition becomes false.
| for loop | while loop |
|---|---|
| Fixed number of iterations | Unknown iterations |
for i in range(5): print(i) count = 0 while count < 5: print(count) count += 1
break do inside a loop?Definition: The break statement immediately stops the loop, even if items remain.
| Statement | Effect |
|---|---|
| break | Exits loop immediately |
for i in range(10): if i == 5: break print(i)
continue do inside a loop?Definition: continue skips the current iteration and moves to the next.
| Statement | Effect |
|---|---|
| continue | Skip this iteration |
for i in range(5): if i == 2: continue print(i)
pass inside loops?Definition: pass does nothing. It is used as a placeholder inside loops or functions.
| Keyword | Purpose |
|---|---|
| pass | Placeholder (no action) |
for i in range(3): pass # does nothing
Definition: A loop inside another loop is called a nested loop, used for matrices or combinations.
| Outer Loop | Inner Loop |
|---|---|
| Runs 3 times | Runs for each outer iteration |
for i in range(3): for j in range(2): print(i, j)
else block in loops?Definition: The else part executes only when the loop completes normally without hitting a break.
Useful for searching operations where you want to detect βnot foundβ.
| Loop Event | Else Executes? |
|---|---|
| Loop ends normally | β Yes |
| Loop breaks early | β No |
nums = [1, 3, 5, 7] for n in nums: if n == 10: break else: print("Number not found")
Definition: Dictionaries can be looped using keys, values, or keyβvalue pairs. Used widely in JSON processing and API data handling.
| Method | Iterates |
|---|---|
| dict.keys() | Keys only |
| dict.items() | Key + Value |
data = {"name": "Hari", "age": 22}
# Method 1
for k in data:
print(k)
# Method 2
for k, v in data.items():
print(k, v)
Definition: Reverse iteration loops backward from last to first. Useful for deleting items safely or scanning data from end.
| Method | Technique |
|---|---|
| reversed() | Recommended way |
| range() | Index-based reverse loop |
nums = [1, 2, 3, 4] # Method 1 for n in reversed(nums): print(n) # Method 2 for i in range(len(nums)-1, -1, -1): print(nums[i])
Definition: Use zip() to pair elements from two lists.
Mostly used in ML (features + labels pairing).
| Function | Purpose |
|---|---|
| zip() | Combines multiple iterables |
names = ["A", "B"] scores = [90, 85] for name, score in zip(names, scores): print(name, score)
enumerate() and a normal loop?Definition: enumerate() returns index + value together.
Useful when looping lists while tracking positions.
| Loop Type | What You Get |
|---|---|
| Normal Loop | Value only |
| enumerate | Index + Value |
items = ["pen", "book"] for i, item in enumerate(items): print(i, item)
Definition: Nested loops can generate every combination between two lists. Useful in ML grid search, coordinate generation, pair matching.
| Input Lists | Output |
|---|---|
| [1,2], ['a','b'] | (1,a), (1,b), (2,a), (2,b) |
nums = [1, 2] chars = ["a", "b"] for n in nums: for c in chars: print((n, c))
Definition: A function is a reusable block of code that performs a specific task. Functions help avoid repetition and improve modularity in programs.
| Part | Meaning |
|---|---|
| def | Starts a function |
| return | Sends back a value |
def greet(): return "Hello" print(greet())
Definition: Parameters are variables in function definition; arguments are actual values passed during calling. Functions use parameters to receive dynamic input.
| Term | Role |
|---|---|
| Parameter | Placeholder variable |
| Argument | Value passed |
def add(a, b): # a, b = parameters return a + b print(add(5, 3)) # 5,3 = arguments
Definition: return sends a result back to the caller and ends the function immediately.
It is essential for passing output to other parts of the program.
| Feature | Explanation |
|---|---|
| Stops function | Code after return wonβt run |
| Sends value | Output available to caller |
def multiply(a, b): return a * b
Definition: Default parameters provide a fallback value when no argument is passed. Useful for optional behaviors or flexible function calls.
| Parameter Type | Behavior |
|---|---|
| Default | Provides backup value |
def greet(name="Guest"): print("Hello", name) greet() greet("Hari")
Definition: *args accepts unlimited positional arguments; **kwargs accepts unlimited keyword arguments.
Useful for flexible function calls.
| Syntax | Meaning |
|---|---|
| *args | Tuple of values |
| **kwargs | Dictionary of key-values |
def demo(*args, **kwargs): print(args) print(kwargs) demo(1,2, name="Hari")
Definition: Recursion is when a function calls itself to solve smaller subproblems. Used heavily in DSA: trees, graphs, factorial, fibonacci.
| Part | Role |
|---|---|
| Base Case | Stops recursion |
| Recursive Case | Calls itself |
def fact(n): if n == 1: return 1 return n * fact(n-1)
Definition: A lambda is a small one-line anonymous function. Used in sorting, filtering, map, reduce operations.
| Type | Feature |
|---|---|
| lambda | One-line, anonymous |
| def | Multi-line, named |
square = lambda x: x*x
print(square(5))
Definition: Scope decides where a variable can be accessed. Local exists inside function; global exists everywhere.
| Type | Access Level |
|---|---|
| local | Inside function only |
| global | Entire program |
x = 10 # global def test(): x = 5 # local print(x)
Definition: Type hints indicate expected data types for parameters and return values. They donβt enforce types but help documentation and debugging.
| Format | Purpose |
|---|---|
| a: int | Parameter hint |
| -> int | Return hint |
def add(a: int, b: int) -> int: return a + b
Definition: Yes, Python functions can return multiple values as a tuple. Helpful when functions compute multiple results at once.
| Feature | Explanation |
|---|---|
| Multiple return | Packed into tuple |
def stats(a, b): return a+b, a*b print(stats(3,4))
Definition: Docstrings describe what a function does, how to use it, and what it returns. They appear when using help() or in documentation tools.
| Part | Description |
|---|---|
| """...""" | Defines docstring |
def greet(): """This function prints greeting message""" print("Hello")
Definition: A higher-order function accepts another function as argument or returns one. Used in map, filter, decorators, and functional programming.
| Feature | Explanation |
|---|---|
| Takes function | Like map(func, list) |
| Returns function | Used in decorators |
def apply(func, x): return func(x) print(apply(lambda n: n+n, 5))
Definition: A lambda function is a small anonymous one-line function created using the lambda keyword.
It is mainly used in sorting, filtering, and functional programming.
| Feature | Meaning |
|---|---|
| Anonymous | No function name |
| Single expression | Cannot contain multiple statements |
square = lambda x: x * x
print(square(6))
Definition: Normal functions use def and support multiple statements,
whereas lambda functions are restricted to one line and do not require a name.
| Normal Function | Lambda Function |
|---|---|
| Multi-line allowed | One-line only |
| Has a name | Anonymous |
def add(a, b): return a + b add2 = lambda a, b: a + b
Definition: map() applies a lambda function to each element
of an iterable and produces a new transformed iterable.
| Function | Purpose |
|---|---|
| map(func, iterable) | Transforms each item |
nums = [1,2,3,4]
doubled = list(map(lambda x: x*2, nums))
print(doubled)
Definition: filter() selects elements based on a boolean condition
defined inside a lambda expression.
| filter() | Purpose |
|---|---|
| filter(func, iterable) | Keeps items that return True |
nums = [1,2,3,4,5,6]
evens = list(filter(lambda x: x%2 == 0, nums))
print(evens)
Definition: List comprehension provides a concise syntax to generate a new list from an iterable using a single readable line.
| Format | Example |
|---|---|
| [expr for item in list] | [x*x for x in nums] |
squares = [x*x for x in range(6)] print(squares)
Definition: List comprehensions can contain inline if statements
to filter or conditionally generate values.
| Type | Example |
|---|---|
| With filter | [x for x in nums if x%2==0] |
evens = [x for x in range(10) if x % 2 == 0] print(evens)
Definition: Dictionary comprehension lets you generate dictionaries using a clear and Pythonic one-line syntax.
| Format | Example |
|---|---|
| {k:v for item} | {x: x*x for x in nums} |
squares = {x: x*x for x in range(5)}
print(squares)
Definition: A set comprehension creates a set using one-line syntax. Unlike lists, sets automatically remove duplicates.
| Comprehension | Unique? |
|---|---|
| List | No |
| Set | Yes |
unique_vals = {x % 3 for x in range(10)}
print(unique_vals)
Definition: A generator expression produces values lazily on demand instead of storing all values in memory like list comprehensions.
| Type | Memory Usage |
|---|---|
| List comprehension | Stores entire list |
| Generator expression | Generates item-by-item |
gen = (x*x for x in range(5)) print(next(gen))
Definition: Nested comprehensions contain multiple for loops inside a single
comprehension, commonly used for matrix flattening or pair generation.
| Use Case | Example |
|---|---|
| Flatten list | [y for x in matrix for y in x] |
matrix = [[1,2],[3,4]] flat = [y for x in matrix for y in x] print(flat)
Definition: Yes, comprehensions allow multiple if clauses
to finely filter elements based on multiple conditions.
| Feature | Meaning |
|---|---|
| Multiple filters | x>0 and x%2==0 |
nums = range(20) result = [x for x in nums if x > 5 if x % 2 == 0] print(result)
Definition: Comprehensions are faster, more readable, and more Pythonic than traditional loops for data transformation tasks.
| Advantage | Reason |
|---|---|
| Speed | Optimized internally |
| Cleaner Code | One-line syntax |
result1 = []
for x in range(10):
result1.append(x*x)
result2 = [x*x for x in range(10)]
Definition:
An exception is a runtime error that interrupts normal program flow and stops execution unless handled.
Exceptions occur when invalid operations happen such as dividing by zero, accessing missing keys, or using wrong types.
| Cause | Example |
|---|---|
| Invalid operation | 10/0 |
| Wrong type | "a" - 3 |
print(10/0) # ZeroDivisionError
Definition:
A tryβexcept block lets you safely run risky code and handle errors gracefully without crashing the program.
It improves stability and user experience by replacing crashes with meaningful messages.
| Block | Purpose |
|---|---|
| try | Risky code |
| except | Handles error |
try:
x = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
Definition:
The else block runs only when no exceptions occur, helping separate normal logic from error logic.
The finally block always executes, useful for cleanup tasks like closing files or database connections.
| Block | When it runs |
|---|---|
| else | If no error |
| finally | Always runs |
try:
val = int("10")
except ValueError:
print("Invalid number")
else:
print("Converted:", val)
finally:
print("Done")
Definition:
An exception object stores error details and moves up the call stack when raised until handled by a matching except block.
If unhandled at all levels, the program terminates with a traceback.
| Stage | Meaning |
|---|---|
| Raised | Error created |
| Propagation | Moves upward |
def level1(): return level2()
def level2(): return level3()
def level3(): raise ValueError("Error happened")
try:
level1()
except ValueError as e:
print("Handled:", e)
Definition:
Multiple except blocks let you handle different error types using separate logic.
Order matters because Python checks them top-to-bottom and the first matching except executes.
| Position | Meaning |
|---|---|
| Top | Most specific exceptions |
| Bottom | General exceptions |
try:
x = int("abc")
except ValueError:
print("Invalid conversion")
except Exception:
print("General error")
Definition:
Exception chaining links a new exception with the original one so developers can track the full error sequence.
The raise new_error from old_error syntax explicitly connects both errors.
| Feature | Benefit |
|---|---|
| Chained exceptions | Complete error history |
| from keyword | Specifies underlying cause |
try:
int("xyz")
except ValueError as e:
raise RuntimeError("Conversion failed") from e
Definition:
A SyntaxError occurs before program execution, caused by invalid Python grammar.
RuntimeError occurs during execution due to unexpected conditions in the logic.
| Error Type | Occurs When |
|---|---|
| SyntaxError | Before execution |
| RuntimeError | During execution |
# SyntaxError example (won't run)
# if True print("Hello")
# RuntimeError example
def f():
return f()
# f() # RecursionError
Definition:
A broad except: catches every error and hides real bugs, making debugging extremely difficult.
Best practice is to handle specific exceptions and use general except only with strong justification.
| Bad Practice | Good Practice |
|---|---|
| except: | except ValueError, TypeError⦠|
try:
x = int("abc")
except:
print("Bad: hides real issues")
try:
x = int("abc")
except ValueError:
print("Good: proper error handling")
Definition:
Manual raising lets developers trigger errors intentionally when invalid inputs or states are detected.
Use raise when you want to enforce rules or stop execution with a clear message.
| Use Case | Why |
|---|---|
| Input validation | Protect program |
| Stop execution early | Prevent damage |
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
set_age(-1)
Definition:
Custom exceptions allow you to define project-specific error types, improving clarity and debugging.
They inherit from Exception and contain meaningful messages relevant to your application.
| Benefit | Meaning |
|---|---|
| Readable errors | Clear intent |
| Better grouping | Handle project-specific failures |
class NegativeBalanceError(Exception):
pass
def withdraw(amount):
if amount < 0:
raise NegativeBalanceError("Cannot withdraw negative amount")
withdraw(-100)
Definition:
Context managers automate setup and cleanup of resources using __enter__ and __exit__.
They ensure resources are released even if exceptions occur inside the managed block.
| Method | Purpose |
|---|---|
| __enter__ | Setup |
| __exit__ | Cleanup |
with open("file.txt") as f:
data = f.read() # file closes even if error occurs
Definition:
Python exceptions follow a hierarchy where BaseException is the root for all error types.
Understanding this structure helps catch errors accurately without over-catching.
| Class | Level |
|---|---|
| BaseException | Root |
| Exception | General errors |
| ValueError, TypeError⦠| Specific |
try:
value = int("Hari")
except Exception as e:
print(type(e))
Theory: A class is a blueprint defining structure and behavior. An object is an instance created from that class with real data loaded into attributes.
| Term | Meaning |
|---|---|
| Class | Design or template |
| Object | Real-world usable instance |
class Car:
def __init__(self, brand):
self.brand = brand
my_car = Car("Tesla")
print(my_car.brand)
Theory: Instance variables belong to a specific object. Class variables are shared across all objects created from the class.
| Type | Stored Where? | Shared? |
|---|---|---|
| Instance variable | Inside object | β No |
| Class variable | Inside class | β Yes |
class Student:
college = "TEKS" # class variable
def __init__(self, name):
self.name = name # instance variable
s1 = Student("Hari")
s2 = Student("Vinay")
print(s1.college, s2.college)
__init__() method?
Theory:
__init__() is automatically called when an object is created.
It initializes the object's attributes and prepares the object for use.
| Feature | Explanation |
|---|---|
| Auto-calls | Runs automatically after object creation |
| Used to initialize | Sets initial values for attributes |
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
e = Employee("Raj", 50000)
print(e.name, e.salary)
Theory:
Encapsulation restricts direct access to class data by using private attributes (__variable)
and exposes controlled access via getter/setter methods.
| Concept | Explanation |
|---|---|
| Private variable | __price β cannot be accessed directly |
| Getter | Reads private data safely |
class Product:
def __init__(self, price):
self.__price = price
def get_price(self):
return self.__price
p = Product(999)
print(p.get_price())
Inheritance allows a class to acquire attributes and methods of another class, reducing duplication and improving code reuse.
| Type | Meaning |
|---|---|
| Single | Child inherits one parent |
| Multiple | Child inherits multiple parents |
class Animal: def speak(self): print("General sound") class Dog(Animal): def speak(self): print("Bark") Dog().speak()
Polymorphism allows the same method call to behave differently depending on the object's class.
| Form | Example |
|---|---|
| Method overriding | Child redefines parent method |
| Duck typing | Objectβs behavior matters, not type |
class Cat: def sound(self): print("Meow") class Dog: def sound(self): print("Bark") for animal in [Cat(), Dog()]: animal.sound()
Abstraction hides internal implementation and exposes only essential features.
| Aspect | Meaning |
|---|---|
| User sees | Simple interface |
| System hides | Complex logic |
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Square(Shape): def area(self): print(4 * 4) Square().area()
Method overriding occurs when a child class redefines a method already defined in its parent class.
| Parent | Child |
|---|---|
| Defines basic method | Gives new behavior |
class A: def show(self): print("Parent show") class B(A): def show(self): print("Child show") B().show()
super() gives access to parent class methods without using the parent class name explicitly.
| Use | Benefit |
|---|---|
| Call parent constructor | Avoids rewriting code |
class A: def __init__(self): print("A init") class B(A): def __init__(self): super().__init__() print("B init") B()
MRO (Method Resolution Order) determines the order in which Python searches classes for methods during inheritance.
| Order | Meaning |
|---|---|
| C3 Linearization | Algorithm to order parent classes |
class A: pass class B(A): pass class C(B): pass print(C.mro())
Dunder methods (double-underscore methods) allow operator overloading, object printing, iteration, comparisons etc.
| Method | Purpose |
|---|---|
| __str__ | User-friendly print |
| __add__ | Operator overloading |
class Book:
def __str__(self):
return "Book Object"
print(Book())
An iterator is an object that implements __iter__() and __next__(), allowing sequential access to items.
| Method | Purpose |
|---|---|
| __iter__() | Returns iterator object |
| __next__() | Returns next value |
nums = iter([1, 2, 3]) print(next(nums)) print(next(nums))
An iterable contains multiple values (list, tuple) but does not produce items one by one.
An iterator produces items sequentially using __next__().
| Iterable | Iterator |
|---|---|
| Has __iter__() | Has __next__() |
| e.g., list, tuple | Created using iter() |
lst = [1,2,3] it = iter(lst) print(next(it))
A custom iterator is created by defining a class that implements __iter__() and __next__().
| Method | Role |
|---|---|
| __iter__() | Returns self |
| __next__() | Returns next element or raises StopIteration |
class Counter:
def __init__(self):
self.num = 1
def __iter__(self):
return self
def __next__(self):
if self.num <= 3:
val = self.num
self.num += 1
return val
raise StopIteration
for i in Counter():
print(i)
A generator is a special function that returns an iterator and produces values lazily using yield.
| Feature | Benefit |
|---|---|
| Lazy evaluation | Saves memory |
| Maintains state | Continues from last yield |
def gen():
yield 1
yield 2
yield 3
for x in gen():
print(x)
return terminates the function immediately.
yield pauses the function and resumes on next call.
| return | yield |
|---|---|
| Ends function | Pauses & resumes |
| Normal value | Generator value |
def f():
return 10
def g():
yield 10
yield 20
Generators are faster for large datasets because they generate values on demand instead of storing all values in memory.
| List | Generator |
|---|---|
| Stores entire data | Yields one value at a time |
| High memory usage | Low memory usage |
lst = [x for x in range(1000000)] # Heavy gen = (x for x in range(1000000)) # Light
send() resumes the generator and sends a value inside the paused yield.
| Function | Use |
|---|---|
| send(value) | Injects data into generator |
def demo():
x = yield "start"
yield x * 10
g = demo()
print(next(g))
print(g.send(5))
yield from allows a generator to delegate part of its operations to another generator or iterable.
| Benefit | Meaning |
|---|---|
| Cleaner code | No nested loops |
def outer():
yield from [1,2,3]
yield 4
for i in outer():
print(i)
Generator expressions look similar to list comprehensions but use parentheses and generate elements lazily.
| List Comp | Generator Expr |
|---|---|
| [x*x for x in range(5)] | (x*x for x in range(5)) |
gen = (x*x for x in range(3)) print(next(gen))
StopIteration is raised by iterators to signal that no more items are available.
| Where raised? | Meaning |
|---|---|
| __next__() | Iteration completed |
it = iter([1]) print(next(it)) # next(it) -> raises StopIteration
Yes, because generators compute values lazily, they can generate infinite sequences without memory issues.
| Feature | Benefit |
|---|---|
| Lazy execution | Infinite is possible |
def infinite():
n = 1
while True:
yield n
n += 1
Generators are widely used when handling big data, file streaming, pipelines, or infinite sequences.
| Use Case | Why Generator? |
|---|---|
| Reading large files | Loads one line at a time |
| ETL pipelines | Generates data step-by-step |
def read_large_file():
with open("big.txt") as f:
for line in f:
yield line
A decorator is a function that wraps another function to extend or modify its behavior without changing the original function's code.
| Feature | Meaning |
|---|---|
| Reusability | Wrap logic for multiple functions |
| Non-intrusive | No change in original code |
def my_decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
@my_decorator
def greet():
print("Hello!")
greet()
When Python sees @decorator, it passes the function below it as an argument to the decorator and replaces it with the returned wrapper.
| Before Decoration | After Decoration |
|---|---|
| f = function | f = decorator(function) |
@deco
def hello():
print("Hi")
# Same as:
hello = deco(hello)
A decorator that accepts arguments requires a wrapper around the decorator itself β making it a three-layer function.
| Layer | Purpose |
|---|---|
| Outer function | Accept decorator arguments |
| Decorator function | Accept target function |
| Wrapper | Execute logic |
def repeat(n):
def decorator(func):
def wrapper():
for _ in range(n):
func()
return wrapper
return decorator
@repeat(3)
def hello():
print("Hi")
functools.wraps copies metadata (__name__, __doc__) from the original function to the wrapper, keeping introspection accurate.
| Without wraps() | With wraps() |
|---|---|
| Loses name, docstring | Preserves metadata |
from functools import wraps
def deco(func):
@wraps(func)
def wrapper():
return func()
return wrapper
Decorators are applied from bottom to top, meaning the closest decorator executes first.
| Order | Execution |
|---|---|
| @A | Second |
| @B | First |
@A
@B
def f():
pass
A class decorator accepts a class instead of a function and returns a modified class.
| Target | Effect |
|---|---|
| Class | Wraps behavior or injects attributes |
def decorate(cls):
cls.tag = "decorated"
return cls
@decorate
class Test:
pass
The wrapper must accept variable arguments so it can decorate any function regardless of parameters.
| Parameter type | Purpose |
|---|---|
| *args | Positional arguments |
| **kwargs | Keyword arguments |
def deco(func):
def wrapper(*args, **kwargs):
print("Before")
return func(*args, **kwargs)
return wrapper
A logging decorator prints information before executing a function.
| Log Type | Purpose |
|---|---|
| Function name | Debugging |
| Parameters | Tracking values |
def log(func):
def wrapper(*args, **kwargs):
print("Calling", func.__name__)
return func(*args, **kwargs)
return wrapper
A time decorator calculates function execution duration using time.time().
| Step | Action |
|---|---|
| Start | Record time |
| End | Print duration |
import time
def timer(func):
def wrapper(*args, **kwargs):
s = time.time()
result = func(*args, **kwargs)
print("Time:", time.time() - s)
return result
return wrapper
The decorator closest to the function wraps it first, and then outer decorators wrap the result.
| Decorator | Execution Order |
|---|---|
| @A | Second |
| @B | First |
@A
@B
def f():
pass
Decorators are widely used for modifying function behavior in frameworks and large systems.
| Use Case | Purpose |
|---|---|
| Logging | Tracking function calls |
| Authentication | Verify access rights |
| Caching | Improve performance |
@login_required
def dashboard():
pass
A decoratorβs wrapper should return the inner functionβs value so the decorated function works normally.
| Requirement | Meaning |
|---|---|
| Return value | Preserves function output |
def deco(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result
return wrapper
Python memory management refers to how Python allocates, stores, and frees memory during program execution using its built-in private heap space.
| Component | Role |
|---|---|
| Private Heap | Stores all Python objects |
| Memory Manager | Allocates memory automatically |
x = 10 # Stored in Python heap
Reference counting is a mechanism where Python keeps track of how many references point to an object. When the count drops to 0 β the memory is freed.
| Action | Effect |
|---|---|
| x = obj | Count +1 |
| del x | Count β1 |
import sys a = [1,2,3] print(sys.getrefcount(a))
Pythonβs garbage collector removes unreachable objects β especially those involved in **circular references**.
| Collector | Role |
|---|---|
| Gen 0 | Short-lived objects |
| Gen 1 & 2 | Long-lived objects |
import gc gc.collect()
A memory leak occurs when Python objects are no longer needed but are not garbage-collected (often due to circular references or global variables).
| Reason | Example |
|---|---|
| Circular references | Objects pointing to each other |
| Uncleared cache | Large list stored globally |
a = [] b = [a] a.append(b) # circular reference
Two or more objects reference each other, preventing their reference counts from reaching zero and causing memory leaks.
| Object A | Object B |
|---|---|
| Points to B | Points to A |
class A:
pass
a1 = A()
a2 = A()
a1.ref = a2
a2.ref = a1 # circular
PyMalloc is Pythonβs specialized memory allocator designed to efficiently manage small memory blocks (β€ 512 bytes).
| Size | Allocation Method |
|---|---|
| Small objects | PyMalloc |
| Large objects | System malloc() |
x = 42 # Allocated via PyMalloc
Python offers tools like memory_profiler and tracemalloc to analyze memory consumption line-by-line.
| Tool | Purpose |
|---|---|
| memory_profiler | Line-by-line memory report |
| tracemalloc | Tracks memory allocations |
import tracemalloc tracemalloc.start()
Shallow copy creates a new object but copies references. Deep copy creates entirely new nested objects.
| Copy Type | Effect |
|---|---|
| Shallow | Shares inner objects |
| Deep | Copies all nested objects |
import copy a = [[1,2]] b = copy.deepcopy(a)
Memory fragmentation occurs when free memory is divided into small blocks, making it hard to allocate large continuous sections.
| Type | Meaning |
|---|---|
| Internal | Unused space inside blocks |
| External | Free space is scattered |
# Python avoids fragmentation using memory pools
| Stack | Heap |
|---|---|
| Stores function calls | Stores Python objects |
| Fast | Slightly slower |
def f():
x = 10 # x in stack, value in heap
Python holds memory blocks in pools for future reuse instead of releasing them back to the OS immediately. This improves performance and reduces fragmentation.
| Reason | Benefit |
|---|---|
| Pool allocator | Faster reuse |
| Avoid fragmentation | Efficient memory layout |
# Python memory is reused internally
| Technique | Why it helps |
|---|---|
| Use generators | Lowers memory footprint |
| del unused vars | Reduces reference count |
| Use tuples instead of lists | Immutables use less memory |
def read_large():
for line in open("big.txt"):
yield line # generator saves memory
Definition: Summing list values is a basic aggregation operation used in many algorithms and data transformations.
| Input | Output |
|---|---|
| [2, 5, 7] | 14 |
nums = [2, 5, 7] total = sum(nums) print(total)
Definition: Vowel counting is a common string-processing task used in pattern recognition and text analysis.
s = "Interview" vowels = "aeiou" count = sum(1 for ch in s.lower() if ch in vowels) print(count)
Definition: Reversing a string manually demonstrates loop usage and iteration logic.
s = "Python"
rev = ""
for ch in s:
rev = ch + rev
print(rev)
Definition: Maximum finding is core logic in selection algorithms.
nums = [4, 8, 2, 9, 1]
mx = nums[0]
for n in nums:
if n > mx:
mx = n
print(mx)
Definition: Prime number logic is widely used in hashing and modular arithmetic.
n = 13
is_prime = True
if n < 2:
is_prime = False
else:
for i in range(2, int(n**0.5)+1):
if n % i == 0:
is_prime = False
break
print(is_prime)
Definition: Word tokenization is important in NLP and preprocessing tasks.
s = "Python is easy to learn" count = len(s.split()) print(count)
Definition: Order-preserving duplicate removal is applied in data cleaning pipelines.
nums = [1,2,2,3,1,4]
seen = set()
unique = []
for n in nums:
if n not in seen:
seen.add(n)
unique.append(n)
print(unique)
Definition: Palindrome logic is used in pattern recognition and text classification.
s = "Madam" s2 = s.lower() print(s2 == s2[::-1])
Definition: Factorial math is used in combinatorics and probability calculations.
n = 5
fact = 1
for i in range(1, n+1):
fact *= i
print(fact)
Definition: Frequency counting is fundamental in hashing and compression algorithms.
s = "programming"
freq = {}
for ch in s:
freq[ch] = freq.get(ch, 0) + 1
print(freq)
Definition: Finding the minimum value manually teaches comparison logic and linear scanning used in basic algorithms.
nums = [5, 2, 9, 1, 7]
mn = nums[0]
for n in nums:
if n < mn:
mn = n
print(mn)
Definition: Space removal is commonly used in text preprocessing and user-input cleaning.
s = "Python is fun"
result = s.replace(" ", "")
print(result)
Definition: Evenβodd classification is a simple arithmetic check used in many number theory tasks.
n = 17
if n % 2 == 0:
print("Even")
else:
print("Odd")
Definition: Frequency counting is core logic in analytics, hashing, and data grouping tasks.
nums = [1,2,3,2,4,2,5] target = 2 count = nums.count(target) print(count)
Definition: List merging is used everywhere in data processing, ETL pipelines, and batch operations.
a = [1, 2, 3] b = [4, 5, 6] merged = a + b print(merged)
Definition: Substring search is foundational for NLP, search engines, and text matching.
s = "Hello Python"
print("Python" in s)
Definition: Mapping each value to a new value is used in transformations and feature engineering.
nums = [1,2,3,4] squares = [n*n for n in nums] print(squares)
Definition: Fibonacci logic is a classic interview question to test loop-based generation patterns.
n = 6
a, b = 0, 1
for _ in range(n):
print(a)
a, b = b, a + b
Definition: String transformation tasks are common in preprocessing, NLP, and cleaning pipelines.
words = ["apple", "banana", "cat"] upper = [w.upper() for w in words] print(upper)
Definition: Index searching is a key skill used in searching algorithms and list operations.
nums = [3,5,7,5,8] target = 5 print(nums.index(target))
Definition: Frequency counting is used in hashing, grouping, NLP token counting, and competitive programming.
nums = [1,2,2,3,3,3]
freq = {}
for n in nums:
if n not in freq:
freq[n] = 1
else:
freq[n] += 1
print(freq)
Definition: Ordered de-duplication is important in databases, streaming logs, and real-time processing.
nums = [1,2,2,3,1,4,3]
result = []
for x in nums:
if x not in result:
result.append(x)
print(result)
Definition: Second-min/second-max logic appears in leaderboard ranking and array problem solving.
nums = [10,4,2,8,15] unique = sorted(set(nums)) print(unique[1])
Definition: Anagram checks are used in NLP preprocessing, string hashing, and interview filtering rounds.
s1 = "listen" s2 = "silent" print(sorted(s1) == sorted(s2))
Definition: Pair-sum problems are core for DSA, 2-pointer algorithms, and coding interviews.
nums = [1,2,3,4,5]
target = 6
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
print(nums[i], nums[j])
Definition: Longest-word extraction is used in NLP, token analysis, and summarization tools.
s = "Python coding interview preparation" words = s.split() longest = max(words, key=len) print(longest)
Definition: Rotation logic appears in circular queues and time-series window shifting.
nums = [1,2,3,4,5] k = 2 k = k % len(nums) rotated = nums[-k:] + nums[:-k] print(rotated)
Definition: Flattening is heavily used in ML preprocessing, matrix-to-vector transformation, and ETL pipelines.
matrix = [[1,2],[3,4],[5,6]] flat = [n for row in matrix for n in row] print(flat)
Definition: Cleaning text is a crucial part of NLP preprocessing and data sanitization.
s = "He!!o P@th#n" clean = "".join(ch for ch in s if ch.isalnum() or ch == " ") print(clean)
Definition: Sorted check is used in binary search, data validation, and signal processing.
nums = [1,2,3,4,5] print(nums == sorted(nums))
Definition: Identifying the first unique element is a common FAANG string-processing problem requiring optimal hashing.
s = "aabbcddeff"
freq = {}
for ch in s:
freq[ch] = freq.get(ch, 0) + 1
for ch in s:
if freq[ch] == 1:
print("First non-repeating:", ch)
break
Definition: Optimized duplicate finding is a very common hashing interview challenge.
nums = [1,2,3,2,4,5,3,6,3]
seen = set()
dups = set()
for n in nums:
if n in seen:
dups.add(n)
else:
seen.add(n)
print(dups)
Definition: Rotational equivalence is used in pattern recognition and cyclic structure checks.
a = [1,2,3,4,5] b = [3,4,5,1,2] check = " ".join(map(str,a)) target = " ".join(map(str,b)) print(target in (check + " " + check))
Definition: Manual tokenization checks your ability to process strings at a low level.
s = "Python is super powerful"
word = ""
words = []
for ch in s + " ":
if ch != " ":
word += ch
else:
words.append(word)
word = ""
print(" ".join(words[::-1]))
Definition: This sliding window pattern appears repeatedly in Amazon & Google interviews.
nums = [1,2,2,3,4,1,2,3,4,5]
best = curr = 1
for i in range(1, len(nums)):
if nums[i] > nums[i-1]:
curr += 1
else:
curr = 1
best = max(best, curr)
print(best)
Definition: Binary search is the most essential DSA concept; FAANG expects clean implementation.
nums = [1,3,5,7,9,11]
target = 7
low, high = 0, len(nums)-1
found = False
while low <= high:
mid = (low + high) // 2
if nums[mid] == target:
found = True
break
elif nums[mid] < target:
low = mid + 1
else:
high = mid - 1
print("Found?", found)
Definition: This uses the BoyerβMoore Voting Algorithm β a very popular interview problem.
nums = [3,3,4,2,3,3,1]
count = 0
candidate = None
for n in nums:
if count == 0:
candidate = n
count += 1 if n == candidate else -1
print("Majority Element:", candidate)
Definition: Matrix manipulation is important in ML, AI pipelines, and basic DSA.
mat = [[1,2,3],[4,5,6],[7,8,9]]
transpose = []
for col in range(len(mat[0])):
row_vals = []
for row in range(len(mat)):
row_vals.append(mat[row][col])
transpose.append(row_vals)
print(transpose)
Definition: Missing-number logic appears in array scanning & validation problems.
nums = [1,2,4,6,7]
start, end = 1, 7
miss = []
for i in range(start, end+1):
if i not in nums:
miss.append(i)
print(miss)
Definition: FAANG tests linked-list logic heavily. Here we simulate using tuples.
linked = [1,2,3,4,5] rev = linked[::-1] print(rev)
Definition: Fast exponentiation reduces time complexity from O(n) β O(log n).
def fast_pow(x, n):
result = 1
while n > 0:
if n % 2 == 1:
result *= x
x *= x
n //= 2
return result
print(fast_pow(2, 10))
Definition: LRU Cache is a top-tier system design + coding interview problem.
capacity = 3
cache = []
store = {}
def access(key, value=None):
if key in store:
cache.remove(key)
else:
if len(cache) == capacity:
old = cache.pop(0)
del store[old]
store[key] = value
cache.append(key)
access("A", 1)
access("B", 2)
access("C", 3)
access("A")
access("D", 4)
print(store)
Definition: Subset generation is a core recursion/backtracking pattern used in FAANG interviews.
nums = [1,2,3]
res = [[]]
for n in nums:
res += [subset + [n] for subset in res]
print(res)
Definition: Two-sum is the most asked DSA question in FAANG coding interviews.
nums = [2,7,11,15]
target = 9
seen = {}
for i, v in enumerate(nums):
diff = target - v
if diff in seen:
print([seen[diff], i])
break
seen[v] = i
Definition: Backtracking for permutation generation is a classic advanced coding test.
res = []
def permute(nums, path=[]):
if not nums:
res.append(path)
return
for i in range(len(nums)):
permute(nums[:i] + nums[i+1:], path + [nums[i]])
permute([1,2,3])
print(res)
Definition: Memoization eliminates repeated calculations, drastically improving recursion performance.
memo = {}
def fib(n):
if n <= 1:
return n
if n in memo:
return memo[n]
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
print(fib(10))
Definition: Palindrome substring detection is a very common string DP/interview pattern.
def longest_pal(s):
res = ""
for i in range(len(s)):
# Odd length
l, r = i, i
while l >= 0 and r < len(s) and s[l]==s[r]:
if (r-l+1) > len(res):
res = s[l:r+1]
l -= 1; r += 1
# Even length
l, r = i, i+1
while l >= 0 and r < len(s) and s[l]==s[r]:
if (r-l+1) > len(res):
res = s[l:r+1]
l -= 1; r += 1
return res
print(longest_pal("babad"))
Definition: Merging overlapping intervals appears in scheduling, memory blocks, and OS allocation algorithms.
intervals = [[1,3],[2,6],[8,10],[15,18]]
intervals.sort()
merged = []
for i in intervals:
if not merged or merged[-1][1] < i[0]:
merged.append(i)
else:
merged[-1][1] = max(merged[-1][1], i[1])
print(merged)
Definition: Uses two-pointer technique to calculate stored water β asked frequently in Amazon/Google.
height = [4,2,0,3,2,5]
left, right = 0, len(height)-1
lmax = rmax = 0
water = 0
while left < right:
if height[left] < height[right]:
if height[left] >= lmax:
lmax = height[left]
else:
water += lmax - height[left]
left += 1
else:
if height[right] >= rmax:
rmax = height[right]
else:
water += rmax - height[right]
right -= 1
print(water)
Definition: One of the toughest sliding-window questions β asked in Meta & Bloomberg.
s = "ADOBECODEBANC"
t = "ABC"
from collections import Counter
need = Counter(t)
missing = len(t)
left = start = end = 0
for right, ch in enumerate(s, 1):
if need[ch] > 0:
missing -= 1
need[ch] -= 1
if missing == 0:
while left < right and need[s[left]] < 0:
need[s[left]] += 1
left += 1
if end == 0 or right-left < end-start:
start, end = left, right
print(s[start:end])
Definition: LCP is used heavily in trie structures, autocomplete systems, and string grouping.
strs = ["flower","flow","flight"]
prefix = strs[0]
for s in strs[1:]:
while not s.startswith(prefix):
prefix = prefix[:-1]
print(prefix)
Definition: Pure recursive permutation generation β testing depth-first logic.
def perm(s, path=""):
if not s:
print(path)
return
for i in range(len(s)):
perm(s[:i] + s[i+1:], path + s[i])
perm("abc")
Definition: A fundamental ML operation and common DSA exam question.
A = [[1,2],[3,4]]
B = [[5,6],[7,8]]
res = [[0,0],[0,0]]
for i in range(2):
for j in range(2):
for k in range(2):
res[i][j] += A[i][k] * B[k][j]
print(res)
Definition: Stack-based bracket matching is essential in compiler design + code parsing.
s = "{[()]}[]"
stack = []
pairs = {')':'(',']':'[','}':'{'}
valid = True
for ch in s:
if ch in "([{":
stack.append(ch)
else:
if not stack or stack[-1] != pairs[ch]:
valid = False
break
stack.pop()
valid = valid and not stack
print(valid)
Definition: Dynamic programming classic β used in optimization interviews.
coins = [1,2,5]
amount = 11
dp = [float("inf")] * (amount+1)
dp[0] = 0
for c in coins:
for a in range(c, amount+1):
dp[a] = min(dp[a], dp[a-c] + 1)
print(dp[amount])
Definition: Backtracking search appears heavily in Amazon/Meta coding rounds.
nums = [2,3,6,7]
target = 7
res = []
def dfs(i, total, path):
if total == target:
res.append(path)
return
if total > target or i == len(nums):
return
dfs(i, total + nums[i], path + [nums[i]])
dfs(i+1, total, path)
dfs(0,0,[])
print(res)
Definition: Kadaneβs is the best-known dynamic programming array technique.
nums = [-2,1,-3,4,-1,2,1,-5,4]
best = curr = nums[0]
for n in nums[1:]:
curr = max(n, curr+n)
best = max(best, curr)
print(best)
Definition: Rotation logic is used in image processing and 2D array manipulation.
mat = [
[1,2,3],
[4,5,6],
[7,8,9]
]
rot = []
for c in range(len(mat)):
row = []
for r in range(len(mat)-1, -1, -1):
row.append(mat[r][c])
rot.append(row)
print(rot)
Definition: This is a top 3 most repeated FAANG question.
s = "abcabcbb"
seen = {}
start = longest = 0
for i,ch in enumerate(s):
if ch in seen and seen[ch] >= start:
start = seen[ch] + 1
seen[ch] = i
longest = max(longest, i - start + 1)
print(longest)
Definition: N-Queens is a core backtracking algorithm asked in senior-level Python roles.
N = 4
res = []
def solve(row, cols, diag1, diag2, path):
if row == N:
res.append(path)
return
for col in range(N):
if col not in cols and (row+col) not in diag1 and (row-col) not in diag2:
solve(row+1, cols|{col}, diag1|{row+col}, diag2|{row-col}, path+[col])
solve(0, set(), set(), set(), [])
print(res)