📌 Key Concepts Overview
| Concept | One-Line Definition |
|---|---|
while loop |
Repeats code as long as a condition is True
|
while True |
Infinite loop — needs break to stop |
break |
Immediately exits the loop |
continue |
Skips current iteration, moves to next |
| List | Ordered, mutable collection — heterogeneous elements allowed |
| List Comprehension | One-line way to build a list using a loop + condition |
| List Mutability | Lists can be changed in place — id() stays the same |
🔁 Part 1 — while Loop
The 3 Components (Critical Pattern)
# 1. Initialisation 2. Condition 3. Increment/Decrement
a = 1 # 1. Initialisation
while a <= 10: # 2. Condition
print('Devops')
a += 1 # 3. Increment
# Without increment → INFINITE LOOP (condition never becomes False)
How it works: Condition is checked before each iteration. As soon as it's False, the loop stops. Miss the increment/decrement → infinite loop (a real production hazard — can hang a script or burn CPU).
while — Practical Patterns
# Countdown (decrement)
a = 10
while a > 0:
print('Devops')
a -= 1
# Sum of 1 to 20
total = 0
a = 1
while a <= 20:
total += a
a += 1
print(total) # 210
# Product (factorial-style) of 1 to 20
product = 1
a = 1
while a <= 20:
product *= a
a += 1
print(product)
# Pattern using while + string repetition
str1 = 'Devops'
i = 0
while i < len(str1):
print(str1[i] * (i + 1))
i += 1
# D
# ee
# vvv
# oooo
# ppppp
# ssssss
for vs while — When to Use Which
Use for
|
Use while
|
|---|---|
| You know the iterable / number of repetitions | You don't know how many times — depends on a condition |
| Looping over list, string, range | Retry logic, polling, waiting for a state |
# DevOps: retry logic — classic while True use case
max_attempts = 5
attempt = 0
while attempt < max_attempts:
print(f'Attempt {attempt+1}: Connecting to server...')
# if connection succeeds: break
attempt += 1
while True — Infinite Loop Pattern
# Always True — runs forever until break is hit
# Used for: retry logic, polling, menu-driven scripts, password validation
password = 'xyz1234'
p = input('Enter your password: ')
while True:
if p == password:
print('Password matched')
break # exits the loop
else:
print('Incorrect password. Try again')
p = input('Enter your password: ')
DevOps real use: polling an AWS resource until it reaches running state, retrying an API call until success, watching a deployment status.
# Simulated polling pattern (real-world shape)
status = 'pending'
checks = 0
while True:
checks += 1
print(f'Check #{checks}: status = {status}')
if status == 'running':
print('✅ Instance is up')
break
if checks >= 5:
print('❌ Timeout waiting for instance')
break
# status = check_instance_status() # in real code
status = 'running' if checks == 3 else 'pending' # simulate
⏭️ Part 2 — Transfer Statements: break & continue
break — Exit the Loop Entirely
lst = [1, 37, 'Python', True, None, 'Devops', 38, 483, 38.438]
# Print elements until None is found, then stop
for i in lst:
if i == None:
break
else:
print(i)
# Output: 1, 37, Python, True (stops at None)
continue — Skip Current Iteration Only
lst = [1, 37, None, 'Python', True, None, 'Devops', 38, 483, None, 38.438]
# Skip None values, but KEEP looping through the rest
for i in lst:
if i == None:
continue # skip this iteration, move to next
else:
print(i)
# Output: 1, 37, Python, True, Devops, 38, 483, 38.438 (all non-None values)
The Key Difference:
| Statement | Effect | Loop continues? |
|---|---|---|
break |
Stops the loop completely | ❌ No — exits |
continue |
Skips just this one iteration | ✅ Yes — moves to next |
break in Nested Loops — Only Breaks the Inner Loop
lst = ['peter', 'piper', 'picked']
# break in inner loop only — outer loop continues
for word in lst:
for char in word:
print(char)
break # breaks INNER loop only — prints first char of each word
# p, p, p (first letter of each word)
# break in outer loop — stops everything after first word
for word in lst:
for char in word:
print(char)
break # breaks OUTER loop — only processes 'peter'
# p, e, t, e, r
# break in both — exits after just ONE character total
for word in lst:
for char in word:
print(char)
break
break
# p (only)
⚠️ Critical for interviews: break/continue only affects the loop they're directly inside — not outer loops.
📦 Part 3 — Lists Deep Dive
List Basics
# Heterogeneous — any mix of types allowed
lst = [1, 37, None, 'Python', True, None, 'Devops', 38, 483, None, 38.438]
print(lst, type(lst))
List Operations
# Concatenation — combine two lists
lst = [1, 2, 3, 4, 5]
lst1 = [6, 7, 8, 9, 10]
print(lst + lst1) # [1,2,3,4,5,6,7,8,9,10]
# ⚠️ + does NOT modify in place — must reassign
lst + [50]
print(lst) # unchanged! still [1,2,3,4,5]
lst = lst + [50] # ✅ now it's updated
# ❌ Can't add a list and a non-iterable directly
# lst + 50 → TypeError: can only concatenate list (not "int") to list
# Repetition
lst = [1, 2, 3, 4, 5]
print(lst * 5) # repeats the whole list 5 times
# Length
print(len(lst)) # 5
# Indexing
print(lst[2]) # 3rd element (0-indexed)
# Slicing
print(lst[::1]) # full copy, same order
print(lst[::-1]) # reversed list
Nested Lists & Deep Indexing
lst = [37, 'Python', 23+37j, True, [1, 36, 'Devops'],
[['Linux', 'AWS', 'Learnbay'], 73, 73.476, 51, 'Hello'],
'Sunday', 'weekend',
['Hello', ['hey', 'WFH', [37, 26.437, False], 'Functions']]]
print(lst[5][4]) # 'Hello' — index 5 → that sub-list → index 4
print(lst[8][1][2][1]) # drilling 4 levels deep
# DevOps relevance: nested JSON/API responses look exactly like this
# e.g., ec2_response['Reservations'][0]['Instances'][0]['State']['Name']
Mutability — The Core List Property
lst = [1, 2, 3, 4, 5]
print(id(lst)) # e.g. 140234...
lst[2] = 'Python' # modify in place — item assignment
print(lst) # [1, 2, 'Python', 4, 5]
print(id(lst)) # SAME id — list was modified, not recreated
Why this matters: Lists are mutable — changing them doesn't create a new object. This is different from strings/tuples (immutable), where any "change" actually creates a brand-new object.
🏗️ Ways to Create a List
# Case 1 — Direct literal (when you know elements)
lst = [1, 2, 34, 5, 6, 'python', True]
# Case 2 — Using list() on an iterable
list('Python') # ['P','y','t','h','o','n']
list((10, 20, 30, 40)) # from a tuple → [10,20,30,40]
list(range(12, 24)) # from a range → [12,13,...,23]
list(input('Enter: ')) # from input string → char by char list
# ⚠️ list() on a single non-iterable-wrapped value vs a list with one item
list(['Python']) # ['Python'] — already a list with 1 string
list('Python') # ['P','y','t','h','o','n'] — string exploded char by char
# Case 3 — List Comprehension (most DevOps-relevant!)
# Syntax: [expression for item in iterable if condition]
lst = [i for i in range(1, 11)] # [1,2,...,10]
lst = [i**2 for i in range(1, 21) if i % 2 == 0] # squares of even numbers 1-20
# Compare to the manual for-loop equivalent:
lst = []
for i in range(1, 11):
lst += [i]
# vs the one-liner:
lst = [i for i in range(1, 11)]
🛠️ List Methods — Full Reference
dir(lst) # shows all available methods on a list object
append() — Add ONE Item to the End
lst = [1, 2, 3, 4, 5]
lst.append(30) # [1,2,3,4,5,30]
lst.append([30, 50]) # adds the WHOLE list as ONE nested item
# [1,2,3,4,5,30,[30,50]] ⚠️ not flattened!
lst.append('Python') # adds as a single string element
# ❌ append() only takes ONE argument
# lst.append(30, 50) → TypeError
extend() — Add Items One-by-One (Flatten an Iterable In)
lst = [100, 200, 300, 400, 500]
lst.extend('Python') # adds each CHARACTER separately
print(lst) # [100,200,300,400,500,'P','y','t','h','o','n']
lst = [1, 2, 3, 4, 5]
lst.extend([30, 50]) # adds each element separately
print(lst) # [1,2,3,4,5,30,50]
append() vs extend() — THE classic interview question:
| Method | Adds | Example |
|---|---|---|
append(x) |
x as a single element (even if x is a list) |
lst.append([1,2]) → [...,[1,2]]
|
extend(x) |
each element of x individually |
lst.extend([1,2]) → [...,1,2]
|
insert() — Insert at a Specific Index
# lst.insert(index, value)
lst = [1, 37, None, 'Python', True]
lst.insert(2, 'Sunday') # inserts BEFORE index 2, shifts rest right
print(lst) # [1, 37, 'Sunday', None, 'Python', True]
lst.insert(-2, 'Python') # works with negative indexing too
count() — Count Occurrences
lst = [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(lst.count(2)) # 3
print(lst.count(1)) # 3
index() — Find Position of First Occurrence
lst = [1, 2, 3, 1, 2, 3]
print(lst.index(2)) # 1 (first occurrence, left to right)
print(lst.index(20)) # ❌ ValueError: 20 is not in list
pop() — Remove by Index (Returns the Removed Value)
# lst.pop(index) — default removes the LAST item
lst = [100, 200, 300, 400, 500]
removed = lst.pop(2) # removes index 2 → returns 300
print(removed) # 300
print(lst) # [100,200,400,500]
lst.pop() # removes LAST item by default
lst.pop(-2) # negative indexing works too
# ❌ Invalid index → IndexError: pop index out of range
remove() — Remove by VALUE (Not Index)
# lst.remove(value) — removes FIRST occurrence, returns nothing
lst = [100, 200, 300, 400, 300, 500]
lst.remove(300) # removes the FIRST 300 only
print(lst) # [100,200,400,300,500]
# ❌ Value not in list → ValueError
pop() vs remove() — Interview Favourite:
| Method | Removes by | Returns | Error if missing |
|---|---|---|---|
pop(index) |
position/index | the removed value | IndexError |
remove(value) |
the value itself | None |
ValueError |
reverse() — Reverses In Place
lst = [1, 2, 3, 4, 5]
lst.reverse()
print(lst) # [5,4,3,2,1] — modifies original list
sort() — Sorts In Place (Ascending by Default)
lst = [72, 48, 216, 4873, 462, 9]
lst.sort() # ascending
lst.sort(reverse=True) # descending
# Works on strings too (alphabetical / ASCII order)
lst = list('coiuhihcbciygdhs')
lst.sort()
# ⚠️ sort() FAILS on mixed/heterogeneous types
lst = [1, 'Python', True, 38.438]
# lst.sort() → TypeError: can't compare str and int
clear() — Empty the List (Same Object, Same id())
lst = [1, 37, 'Python', True]
print(id(lst))
lst.clear()
print(lst) # []
print(id(lst)) # SAME id — emptied in place, not a new object
copy() — Create an Independent Copy (CRITICAL CONCEPT)
# ❌ WITHOUT copy() — both variables point to the SAME list (same id)
lst = [1, 2, 3, 4, 5]
lst1 = lst # NOT a copy — just another name for the same object
lst1[2] = 93
print(lst1) # [1,2,93,4,5]
print(lst) # [1,2,93,4,5] ⚠️ original also changed!
# ✅ WITH copy() — independent list, different id
lst = [1, 2, 3, 4, 5]
lst1 = lst.copy() # new object, different id
lst1[2] = 93
print(lst1) # [1,2,93,4,5]
print(lst) # [1,2,3,4,5] ✅ original untouched
This is one of the most important bugs to understand in Python. lst1 = lst does NOT copy — it just creates a second reference to the exact same list in memory.
Manual String Split (Building Towards .split())
# Class built this manually to understand the LOGIC behind str.split()
str1 = 'Peter piper picked a pack of pickeled peppers'
lst = []
res = ''
for char in str1:
if char != ' ':
res += char
else:
lst.append(res)
res = ''
lst.append(res) # don't forget the last word!
print(lst)
# ['Peter', 'piper', 'picked', 'a', 'pack', 'of', 'pickeled', 'peppers']
# This is literally what str.split() does internally:
print(str1.split()) # same result, one line
☁️ DevOps / Cloud Use Cases
# 1. Poll AWS instance status until running (while True pattern)
status = 'pending'
attempts = 0
while True:
attempts += 1
if status == 'running':
print('✅ Instance ready')
break
if attempts > 10:
print('❌ Timeout — instance not ready')
break
# status = get_instance_status() # real AWS SDK call
# 2. Retry failed deployment with break on success
max_retries = 3
attempt = 0
while attempt < max_retries:
attempt += 1
print(f'Deploy attempt {attempt}...')
success = (attempt == 2) # simulated
if success:
print('✅ Deployed')
break
else:
print('❌ All retries failed')
# 3. Filter out failed health checks using continue
servers_status = ['ok', 'fail', 'ok', None, 'ok', 'fail']
for status in servers_status:
if status is None or status == 'fail':
continue
print(f'✅ Server status: {status}')
# 4. Build list of running instance IDs with list comprehension
instances = [
{'id': 'i-001', 'state': 'running'},
{'id': 'i-002', 'state': 'stopped'},
{'id': 'i-003', 'state': 'running'},
]
running_ids = [inst['id'] for inst in instances if inst['state'] == 'running']
print(running_ids) # ['i-001', 'i-003']
# 5. extend() to merge server lists from multiple regions
us_servers = ['web-us-1', 'web-us-2']
eu_servers = ['web-eu-1', 'web-eu-2']
all_servers = []
all_servers.extend(us_servers)
all_servers.extend(eu_servers)
print(all_servers)
# 6. copy() before modifying a shared config list
base_ports = [22, 80, 443]
web_ports = base_ports.copy()
web_ports.append(8080)
print(base_ports) # unaffected — still [22, 80, 443]
print(web_ports) # [22, 80, 443, 8080]
❌ Common Mistakes
| Mistake | Code | Fix |
|---|---|---|
Forgetting increment in while
|
while a<=10: print(a) (no a+=1) |
Infinite loop — always increment/decrement |
lst + [x] doesn't modify list |
lst + [50] then expects lst changed |
Reassign: lst = lst + [50]
|
append() vs extend() confusion |
lst.append([1,2]) → adds nested list, not 2 items |
Use extend() to flatten in |
lst1 = lst thinking it's a copy |
Modifying lst1 also changes lst
|
Use lst1 = lst.copy()
|
sort() on mixed types |
[1,'a',True].sort() |
TypeError — sort only same-type lists |
pop() on empty/invalid index |
[].pop() |
IndexError — check length first |
remove() on missing value |
lst.remove(999) when not present |
ValueError — check in first |
break only exits ONE loop level |
Expecting break to exit nested loops fully |
Need a flag or function return for full exit |
🎯 Interview Points
"Difference between
append()andextend()?"
→append()adds its argument as a single element (even a whole list becomes one nested item).extend()iterates through its argument and adds each element individually."Difference between
breakandcontinue?"
→breakexits the loop entirely.continueskips only the current iteration and proceeds to the next one."Difference between
pop()andremove()?"
→pop(index)removes by position and returns the value; default removes the last item.remove(value)removes the first matching value and returns nothing."What happens when you do
lst1 = lstvslst1 = lst.copy()?"
→lst1 = lstmakeslst1point to the SAME object — changes to one affect the other..copy()creates an independent object with a newid()."Why are lists mutable?"
→ Their contents can be changed after creation (id()stays the same) — unlike strings/tuples where any change creates a new object."What's a list comprehension and why use it?"
→ A concise one-line way to build a list:[expr for item in iterable if condition]. More Pythonic and often faster than a manual for-loop + append."When would you use
while Trueover a regularwhile?"
→ When the exit condition is checked INSIDE the loop body rather than at the top — e.g., retry logic, polling, "ask again until valid input."
📚 Knowledge Base — Quick Revision
# ── WHILE LOOP ──────────────────────────────────────────────
a = 1 # 1. init
while a <= 10: # 2. condition
...
a += 1 # 3. increment — NEVER forget this
while True: # infinite — needs break
if condition:
break
# ── BREAK / CONTINUE ────────────────────────────────────────
break # exits the loop entirely
continue # skips current iteration, loop continues
# ── LIST CREATION ───────────────────────────────────────────
lst = [1, 2, 3] # literal
lst = list('abc') # from string → ['a','b','c']
lst = list(range(5)) # from range
lst = [i for i in range(10)] # comprehension
lst = [i**2 for i in range(10) if i%2==0] # comprehension + filter
# ── LIST OPERATIONS ─────────────────────────────────────────
lst + lst2 # concatenation (new list, must reassign)
lst * 3 # repetition
len(lst) # length
lst[i] # indexing
lst[::-1] # reverse via slicing
# ── LIST METHODS (mutating, in-place) ───────────────────────
lst.append(x) # add ONE item to end
lst.extend(iterable) # add items individually
lst.insert(i, x) # insert before index i
lst.pop(i) # remove by index, RETURNS value (default: last)
lst.remove(x) # remove by VALUE, first match, returns None
lst.reverse() # reverse in place
lst.sort() # ascending; sort(reverse=True) for descending
lst.clear() # empty the list (same id)
lst.count(x) # occurrences of x
lst.index(x) # first index of x (ValueError if missing)
lst.copy() # independent shallow copy (new id)
# ── CRITICAL GOTCHA ─────────────────────────────────────────
lst1 = lst # ❌ same object — NOT a copy
lst1 = lst.copy() # ✅ independent copy
🏋️ Practice Questions
Easy
- Write a
whileloop that prints numbers from 10 down to 1. - Given
lst = [10, 20, 30, 40, 50], useappend()to add60, thenextend()to add[70, 80]. Print the list after each step. - Use a list comprehension to create a list of cubes (x³) for numbers 1 to 10.
Medium
- Write a
while Trueloop that keeps asking the user to enter a number until they enter a number greater than 100, then breaks and prints "Valid number entered." - Given
lst = [5, 10, None, 15, None, 20], write a loop usingcontinueto print only non-Nonevalues, and a separate loop usingbreakto print values until the firstNone. - Demonstrate the
lst1 = lstvslst1 = lst.copy()bug with code: create a list, assign it without.copy(), modify the copy, and show the original also changed. Then fix it using.copy().
DevOps-Focused
-
Retry Connector: Write a
whileloop that simulates connecting to a server. It should try up to 5 times. Use a variableconnected = Falsethat becomesTrueon the 3rd attempt (simulated). Print each attempt, and break with a success message once connected, or a failure message after 5 failed attempts. -
Server List Builder: You have two lists:
prod_servers = ['web-01','web-02']andstaging_servers = ['stg-01','stg-02']. Useextend()to build one combinedall_serverslist. Then use a list comprehension to createprod_onlycontaining only servers that start with'web'. Print both results.
Top comments (0)