Three real slices of Module 1: the revision notes, the explained Q&A, and the rapid-fire mock. Tap an option on any question — every lesson works like this.
A Python interview is you talking — "what's the difference between a list and a tuple", "why is this printing that", "walk me through your project". So every question in this pack is written the way it's actually asked, and every answer gives you the words to say, the follow-up that comes next, and the thing that loses candidates the round. Try the two below; this is exactly how every lesson behaves.
Q1 What does this print, and why? 📣 Reported in: TCS, Infosys, Accenture, Capgemini, Zoho Python rounds
def add(item, items=[]):
items.append(item)
return items
print(add(1))
print(add(2))
Say this: "[1] then [1, 2]. The default value is evaluated once, when the function is defined — not on every call. So both calls append to the same list object, and the second one sees what the first one left behind."
Say the fix without being asked — that's what scores: "The fix is to default to None and build the list inside the function."
def add(item, items=None):
if items is None:
items = []
items.append(item)
return items
Follow-up: "Which defaults are safe, then?" → Immutable ones — a number, a string, a tuple, None. Never a list, dict or set.
Follow-up: "Why does Python work this way?" → Because the def line is executed once, like any other statement, and the default is just an expression evaluated at that moment. It's consistent — it only surprises you when the object is mutable.
Don’t say: "[1] then [2]." That assumes a fresh list per call, and it's the single most-asked Python fresher trap — the interviewer is watching for exactly this answer.
Q2 Which method runs when you write Student("Asha")? 📣 Reported in: Infosys, Wipro, Cognizant, HCL
Say this: "__init__ — but strictly, two things happen. __new__ creates and returns the instance first, then __init__ initialises it, receiving that instance as self along with the argument. In everyday code I only write __init__; I'd override __new__ for a singleton or when subclassing an immutable type like tuple."
Follow-up: "So is __init__ the constructor?" → Most teams call it that and it's accepted. The precise answer is that __new__ constructs and __init__ initialises — and saying that one line puts you above most candidates.
Follow-up: "What's __str__ versus __repr__?" → __str__ is the readable form for users, used by print(). __repr__ is the unambiguous developer form, used in the REPL and inside containers. If only __repr__ exists, print falls back to it — so if you write one, write that.
Don’t say: "__call__." That runs when you call an instance like a function — obj(), not Student(...).
The full pack has revision notes + 125 explained interview questions like these across Core Python & OOP, data structures and functions, advanced Python (decorators, generators, the GIL), Django/Flask/FastAPI & REST, and SQL with Python, 62 mock-round questions including a predict-the-output round, 30 coding-round programs — each with the approach to say out loud, a working solution, the follow-up that comes after, and the mistake that costs the mark — and a company-wise module covering what TCS, Infosys, Wipro, Accenture, Capgemini, Cognizant, HCL, Tech Mahindra, Zoho, Persistent, Nagarro, Amazon, Flipkart, Paytm and startups actually ask, with every answer written out. No multiple choice anywhere, because no interviewer gives you options.
Every module gives you two study lessons: a 📖 full-notes lesson that teaches the topic in depth, and a ⚡ cheat sheet like this one for quick revision before the Q&A. Here are the first 3 sections of the Core Python & OOP cheat sheet from Module 1 (Core Python & OOP).
⚡ Cheat sheet · quick revision before the Q&APython interviews for freshers start with "is Python compiled or interpreted", "mutable vs immutable", "== vs is", then move to classes: self, __init__, inheritance, MRO, dunder methods, @staticmethod vs @classmethod. These notes give you the mental model (everything is an object, variables are names) so the trick questions stop being tricks.
.pyc in __pycache__) and the Python Virtual Machine executes it. Say "compiled to bytecode, then interpreted".python is); PyPy (JIT, faster), Jython, IronPython. Dynamically typed (type checked at runtime, names can rebind to any type) and strongly typed ("1" + 1 is a TypeError — no silent coercion).print() function, true division /, Unicode strings by default, range is lazy. Current: Python 3.12/3.13 — f-strings, type hints, match statement (3.10), walrus := (3.8).pip + virtual environments (python -m venv .venv) isolate dependencies; requirements.txt.a = [1, 2, 3]
b = a # b is another NAME for the same list object
b.append(4)
print(a) # [1, 2, 3, 4] — no copy happened
c = a[:] # shallow copy (new list, same element objects); import copy; copy.deepcopy(a) for nested
x = 5; y = 5
print(x == y, x is y) # True True (small ints −5..256 are cached — don't rely on it)
s = "hello"; t = "hello"
print(s == t) # True — compare values with ==, identity with `is` only for None/singletons
Assignment never copies. == compares values, is compares identity (same object, same id()). Use is None, never == None. Function arguments are passed by object reference ("pass by assignment"): mutating a passed list is visible to the caller; rebinding the parameter is not.
| Type | Mutable? | Ordered? | Notes |
|---|---|---|---|
| int, float, bool, complex | no | — | arbitrary-precision ints; bool is a subclass of int (True + True == 2) |
| str | no | yes | every "change" makes a new string |
| tuple | no | yes | hashable if contents are; (1,) one-element |
| list | yes | yes | dynamic array; O(1) append/index, O(n) insert/pop(0) |
| dict | yes | insertion order (3.7+) | hash table; keys must be hashable (immutable) |
| set / frozenset | yes / no | no | unique hashable elements; O(1) membership |
| bytes / bytearray | no / yes | yes | binary data |
| None | — | — | singleton; functions without return return None |
Why it matters: immutable objects are safe as dict keys/set members and as default arguments; mutable defaults are shared across calls (the classic bug); strings in loops → use ''.join(list).
🔒 7 more sections in the full notes:
+ 2 quick-check interview questions at the end of every study lesson, a ⚡ cheat sheet like this in every module — and a 📖 full-notes lesson that teaches the whole topic in depth before you revise.
After the notes and cheat sheet comes the Q&A lesson — 25 questions asked the way an interviewer asks them, each with a model answer, the follow-up probe and what not to say. Answer aloud first, then open the answer. Here are the first 5 from Module 1.
Q1 Is Python a compiled language or an interpreted one? 🏢 TCS, Infosys
Say this: "Both, really. When I run a .py file, Python first compiles the source into bytecode — that's what the .pyc files in __pycache__ are — and then the Python Virtual Machine interprets that bytecode. So there is a compile step, but it produces bytecode, not machine code, and it happens automatically. I never invoke a compiler myself the way I would with C."
Follow-up: "Then why is Python called slow?" → Because the PVM interprets bytecode instruction by instruction instead of running native machine code, and every value is a full object with dynamic type lookup. PyPy adds a JIT that compiles hot code to machine code, which is why it can be several times faster on tight loops.
Follow-up: "Is Python dynamically typed or weakly typed?" → Dynamically and strongly typed. Dynamic means a variable's type is known at runtime, not declared. Strong means it won't silently coerce — "5" + 5 raises TypeError, unlike JavaScript.
Don’t say: "It's interpreted line by line." That's the schoolbook answer and the follow-up is always "then what is __pycache__?" — mention bytecode and the PVM and you've already answered the next two questions.
Q2 What's the difference between a list and a tuple? When would you pick a tuple? 🏢 Wipro, Accenture
Say this: "A list is mutable — I can append, remove, sort in place. A tuple is immutable once created. Because a tuple can't change, it's hashable, so it can be a dictionary key or a set member, which a list can never be. Tuples are also slightly faster to create and use a bit less memory. I use a list for a collection of similar things that grows, and a tuple for a fixed record — coordinates, a row from a database, a function returning two values."
Follow-up: "Is a tuple always hashable?" → No. A tuple containing a list isn't — hash((1, [2])) raises TypeError. Hashability is recursive; the tuple is only as hashable as its contents.
Follow-up: "What is (1)?" → An integer. The comma makes the tuple, not the brackets — (1,) is the one-element tuple. That's a real bug source when a function is meant to return a single-item tuple.
Don’t say: "A tuple is a read-only list." It suggests they're interchangeable. The interesting part is why immutability matters: hashability and intent.
Q3 == versus is — what's the difference? 🏢 Cognizant, Capgemini
Say this: "== compares values — it calls the object's __eq__. is compares identity: whether both names point to the same object in memory, the same id(). So a = [1]; b = [1] gives a == b True but a is b False — equal contents, two different objects. In practice I only use is for None, True and False."
Follow-up: "But a = 5; b = 5; a is b gives True — explain." → CPython caches small integers from −5 to 256 and interns short strings, so both names happen to point to the same cached object. It's an implementation detail, not a language guarantee — try it with 1000 in a script and it may be False. That's exactly why is must never be used for value comparison.
Follow-up: "Why if x is None and not if x == None?" → Identity is faster, and there is exactly one None object, so it can't be fooled by a class that overrides __eq__ to return True for everything.
Don’t say: "They're the same thing." It's the single most common trap in Python screening rounds and answering it wrong ends the language section early.
Q4 What does this print, and why? 🏢 TCS, HCL
a = [1, 2, 3]
b = a
b += [4]
print(a)
Say this: "[1, 2, 3, 4]. b = a doesn't copy anything — both names point to the same list object. And += on a list is an in-place operation, it calls __iadd__, which is basically extend. So the one shared list is mutated and a sees it."
Follow-up: "Change it to b = b + [4]." → Then it prints [1, 2, 3]. + builds a brand-new list and rebinds b to it; a still points at the original. That asymmetry between += and + only exists for mutable types.
Follow-up: "What about a tuple or a string?" → They're immutable, so += can't mutate — it always creates a new object and rebinds the name. Same operator, completely different behaviour depending on mutability.
Don’t say: "b is a copy of a." Assignment in Python never copies. To copy, say b = a[:], list(a) or copy.copy(a).
Q5 Shallow copy versus deep copy? 🏢 Infosys, Wipro
Say this: "A shallow copy — a[:], list(a), copy.copy(a) — creates a new outer container but the elements inside are the same objects. A deep copy, copy.deepcopy(a), walks the whole structure and copies the nested objects too. So for a flat list of numbers the two are identical in effect; the difference only shows up when the list holds other mutable objects."
m = [[1], [2]]
s = m[:] # shallow
s[0].append(9)
print(m) # [[1, 9], [2]] -- the inner list is shared
import copy
d = copy.deepcopy(m)
d[0].append(7)
print(m) # unchanged
Follow-up: "When would you avoid deepcopy?" → It's slow on large structures and it copies things you may not want copied — open connections, big caches. Often the cleaner fix is to build a fresh object rather than deep-copy an old one.
Follow-up: "How does deepcopy handle a cycle — a list containing itself?" → It keeps a memo dictionary of already-copied objects, so it handles cycles without infinite recursion.
Don’t say: "a[:] makes a full copy." It makes a new list of the same inner objects — that's the whole point of the question.
🔒 20 more explained questions in this lesson — and a 25-question Q&A lesson like this in every tech module, plus the company-wise module with every answer written out.
Every module ends with a mock interview — answer each aloud in 30–60 seconds, then check the key points. First 3 of the 12-question Module 1 mock:
Q1 Name Python's immutable built-in types.
Good answer hits: int, float, str, tuple, frozenset, bytes — and that list, dict and set are the mutable ones. Immutable objects can be dict keys because they're hashable.
One-liner: "Numbers, strings, tuples and frozensets are immutable; lists, dicts and sets are not."
Follow-up they add: "Is a tuple always hashable?" → No — not if it contains a list.
Q2 What type does 1 / 2 return?
Good answer hits: A float, 0.5 — / is true division in Python 3, regardless of the operand types. // is the one that floors and gives an int for int operands.
One-liner: "float — / is always true division in Python 3; // is floor division."
Follow-up they add: "And in Python 2?" → It returned 0, which is the single most famous 2-to-3 difference.
Q3 Which operator compares identity rather than value?
Good answer hits: is — it compares object identity, the same id(). == compares value by calling __eq__. Use is only for None, True and False.
One-liner: "is for identity, == for value — and only ever is None."
Follow-up they add: "Why does a = 5; b = 5; a is b give True?" → Small-integer caching, an implementation detail you must never rely on.
🔒 9 more mock questions in this lesson, and a mock at the end of every module.
Full course
7 modules · 31 lessons · lifetime access
Every module: notes → 25 explained Q&A → mock. Plus the practical round and the company-wise module (TCS, Infosys, Wipro, Accenture…). 7-day money-back guarantee.