1. Python Basics & Why “Efficiency” Matters

Welcome to the very first step of our Data-Structures & Algorithms journey. In this post we’ll look at everyday Python snippets, measure how much work the computer really does, and then learn tiny tricks that shrink that work from “forever” to “blink-of-an-eye”.
Counting the Invisible Work
Every line in your program costs time. Instead of seconds, let’s count primitive operations (add, compare, jump, etc.).
# Simple cumulative sum
total = 0
for i in range(n):
total += i
print(total)
# Nested loops – quadratic growth
total = 0
for i in range(n):
for j in range(n):
total += 1
The first example grows linearly with n
, the second quadratically. Our job: keep the curve as flat as possible.
The gcd Story – Four Versions, Four Speeds
1. Brute list of common factors
def gcd_v1(a, b):
commons = []
for k in range(1, min(a, b)+1):
if a % k == 0 and b % k == 0:
commons.append(k)
return commons[-1] # last element
2. Same idea, no list kept
def gcd_v2(a, b):
largest = 1
for k in range(1, min(a, b)+1):
if a % k == 0 and b % k == 0:
largest = k
return largest
Both still loop min(a,b)
times – too slow for big numbers.
3. Subtraction trick
def gcd_v3(a, b):
if b == 0:
return a
return gcd_v3(b, a - b)
Still painful: gcd_v3(1, 1000000)
makes a million recursive calls.
4. Euclid’s modulus magic
def gcd_fast(a, b):
while b:
a, b = b, a % b
return a
Only logarithmic steps – a huge win!
Prime or Not? – Squeezing the Loop
# Naïve – check every number up to n-1
def is_prime_slow(n):
for k in range(2, n):
if n % k == 0:
return False
return True
Improvement 1: stop at n//2
.
Improvement 2: stop at √n
– because factors come in pairs.
import math
def is_prime(n):
if n < 2:
return False
k = 2
while k * k <= n:
if n % k == 0:
return False
k += 1
return True
Handling Life’s Little Surprises – Exceptions
try:
num = int(input("Enter an integer: "))
inverse = 1 / num
except ValueError:
print("That wasn’t an integer!")
except ZeroDivisionError:
print("Can’t divide by zero.")
else:
print("Inverse:", inverse)
Objects – Blueprints for Your Own Data Types
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def move(self, dx, dy):
self.x += dx
self.y += dy
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
q = Point(-1, 2)
print("p + q =", p + q)
A Tiny Timer Class
import time
class Stopwatch:
def __init__(self):
self._start = None
self._elapsed = None
def start(self):
if self._start is not None:
raise RuntimeError("Timer already running")
self._start = time.perf_counter()
def stop(self):
if self._start is None:
raise RuntimeError("Timer not started")
self._elapsed = time.perf_counter() - self._start
self._start = None
def read(self):
if self._elapsed is None:
raise RuntimeError("No measurement yet")
return self._elapsed
def __str__(self):
return f"{self.read():.6f} s"
Usage:
import sys
sys.setrecursionlimit(100_000)
t = Stopwatch()
t.start()
print(gcd_fast(678912345678912345, 987654321987654321))
t.stop()
print("Time:", t)
Why All This Matters in the Real World
- Sorting a billion Aadhaar numbers? A quadratic algorithm will still be running next year.
- Searching petabytes of medical data? Every microsecond saved means earlier diagnoses.
- Real-time gaming? 60 FPS allows at most 16 ms per frame – no room for slow code.
“Measure first, optimize second, and always keep the big-O in the corner of your eye.”