Control Flow

How different languages handle the flow of execution, through conditional statements, loops and errors.

Python Language

TypeScript Control-Flow


Conditional Statements

Control the flow of execution based on conditions.

def check_number(num: int) -> str: if num > 0: return "Positive" elif num < 0: return "Negative" else: return "Zero" print(check_number(10)) # "Positive" print(check_number(-5)) # "Negative" print(check_number(0)) # "Zero"
function checkNumber(num: number): string { if (num > 0) { return "Positive"; } else if (num < 0) { return "Negative"; } else { return "Zero"; } }

Ternary Operator or Select

A shorthand for conditional expressions.

Ternary Operator

result = "Positive" if num > 0 else "Negative" if num < 0 else "Zero"

Ternary operator:

const result = num > 0 ? "Positive" : num < 0 ? "Negative" : "Zero";

Switch / Case

A control structure for multi-way branching.

A control structure for multi-way branching.

The switch statement allows you to execute different blocks of code based on the value of an expression.

function getDayName(day: number): string { switch (day) { case 0: return "Sunday"; case 1: return "Monday"; case 2: return "Tuesday"; case 3: return "Wednesday"; case 4: return "Thursday"; case 5: return "Friday"; case 6: return "Saturday"; default: return "Invalid day"; } } console.log(getDayName(0)); // Output: Sunday console.log(getDayName(6)); // Output: Saturday console.log(getDayName(7)); // Output: Invalid day

Loop Over - Iterables

Iterate over elements in a collection or iterable.

Python supports multiple looping constructs.

Basic for loop (iterates over a range of numbers):

for i in range(5): print(i)

Loop over lists (for...in):

numbers = [1, 2, 3] for num in numbers: print(num)

Loop over dictionary keys and values:

user = {"id": 1, "name": "Alice"} for key, value in user.items(): print(f"{key}: {value}")

Enumerate iteration (index + value):

fruits = ["apple", "banana", "cherry"] for index, fruit in enumerate(fruits): print(index, fruit)

TypeScript supports multiple loop constructs.

Basic for loop:

for (let i = 0; i < 5; i++) { console.log(i); }

for...of (iterates over iterable objects):

const numbers: number[] = [1, 2, 3]; for (const num of numbers) { console.log(num); }

for...in (iterates over object keys):

const user = { id: 1, name: "Alice" }; for (const key in user) { console.log(`${key}: ${user[key as keyof typeof user]}`); }

Loop Conditional Exit

Exit a loop based on a condition.

Python does not have a built-in do...while loop like some other programming languages. However, you can achieve similar functionality using a while loop with a condition that is checked after the first iteration.

Example: Using a while loop for conditional exit:

count = 0 while count < 5: print(count) count += 1

Emulating a do...while loop:

To emulate a do...while loop (where the condition is checked after the first iteration), you can use a while True loop with a break statement:

count = 0 while True: print(count) count += 1 if count >= 5: break
for i in range(10): if i == 5: break print(i) # Output: 0, 1, 2, 3, 4
for i in range(5): if i == 2: continue print(i) # Output: 0, 1, 3, 4

TypeScript allows breaking out of loops based on conditions and also supports do...while loops, which execute at least once before checking the condition.

Basic do...while loop:

do { console.log("Executing at least once"); } while (false);

do...while with condition:

let i = 0; do { console.log(i); i++; } while (i < 5); ``` breaking out of loops based on conditions. ```typescript for (let i = 0; i < 10; i++) { if (i === 5) { break; // Exit loop when i equals 5 } console.log(i); }

Using continue to skip iterations:

for (let i = 0; i < 10; i++) { if (i % 2 === 0) { continue; // Skip even numbers } console.log(i); }

Co-Routine - Yield

Pause and resume execution at specific points.

Coroutines and `yield`

Coroutines in Python are a special type of function that can pause and resume execution. They are often used for asynchronous programming and cooperative multitasking. The yield keyword is used to create generators, which are a type of coroutine.

Using yield to Create Generators: - The yield keyword allows a function to return a value and pause its execution, resuming from where it left off when called again.

def count_up_to(n): count = 0 while count < n: yield count count += 1 # Using the generator for num in count_up_to(5): print(num) # Output: 0, 1, 2, 3, 4

Coroutines are functions that can consume values sent to them using the send() method. They are defined using the yield keyword.

def coroutine_example(): print("Coroutine started") while True: value = yield print(f"Received: {value}") # Using the coroutine coro = coroutine_example() next(coro) # Start the coroutine coro.send(10) # Send a value to the coroutine coro.send(20) # Send another value

yield from for Delegating Generators. The yield from statement is used to delegate part of a generator's operations to another generator.

def sub_generator(): yield 1 yield 2 yield 3 def main_generator(): yield from sub_generator() yield 4 # Using the generator for value in main_generator(): print(value) # Output: 1, 2, 3, 4

Asynchronous Coroutines with async def. In modern Python, coroutines are often used with async def and await for asynchronous programming.

import asyncio async def async_example(): print("Start") await asyncio.sleep(1) # Simulate an asynchronous operation print("End") # Running the coroutine asyncio.run(async_example())

TypeScript does not support native coroutines or yield, but it supports generator functions which behave similarly.

function* numberGenerator(): Generator<number> { yield 1; yield 2; yield 3; } const gen = numberGenerator(); console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3

Using yield in a loop:

function* infiniteCounter(): Generator<number> { let i = 0; while (true) { yield i++; } } const counter = infiniteCounter(); console.log(counter.next().value); // 0 console.log(counter.next().value); // 1 console.log(counter.next().value); // 2

Generators provide a way to implement lazy iteration, similar to coroutines in other languages.


Exceptions

Handle errors or exceptional conditions in a structured way.

Exception Handling

Python provides a robust mechanism for handling runtime errors using try, except, else, and finally blocks. This allows you to gracefully handle errors and ensure proper cleanup.

Basic Exception Handling: - the try block lets you test a block of code for errors, and the except block lets you handle the error.

try: result = 10 / 0 except ZeroDivisionError as e: print(f"Error: {e}") # Output: Error: division by zero

Catching Multiple Exceptions. You can catch multiple exceptions by specifying them in a tuple.

try: num = int("abc") result = 10 / 0 except (ValueError, ZeroDivisionError) as e: print(f"Error: {e}") # Output: Error: invalid literal for int() with base 10: 'abc'

Using else with try. The else block runs if no exceptions are raised in the try block.

try: result = 10 / 2 except ZeroDivisionError as e: print(f"Error: {e}") else: print(f"Result: {result}") # Output: Result: 5.0

Using finally for Cleanup. The finally block always executes, regardless of whether an exception was raised or not. It is typically used for cleanup operations.

try: file = open("example.txt", "r") content = file.read() except FileNotFoundError as e: print(f"Error: {e}") finally: print("Closing the file.") if 'file' in locals() and not file.closed: file.close() # Output: Error: [Errno 2] No such file or directory: 'example.txt' # Closing the file.

Raising Exceptions: - You can raise exceptions explicitly using the raise keyword.

def check_positive(num): if num < 0: raise ValueError("Number must be positive") return num try: check_positive(-5) except ValueError as e: print(f"Error: {e}") # Output: Error: Number must be positive

Custom Exceptions: - You can define your own exceptions by creating a custom class that inherits from the Exception class.

class CustomError(Exception): pass try: raise CustomError("This is a custom error") except CustomError as e: print(f"Error: {e}") # Output: Error: This is a custom error

TypeScript, like JavaScript, supports exception handling using try, catch, and finally blocks. These constructs allow you to handle runtime errors gracefully.

try { const result = 10 / 0; // This won't throw an error, but you can handle logic here console.log(result); throw new Error("Something went wrong!"); // Manually throw an error } catch (error) { console.error("Caught an error:", error.message); } finally { console.log("This block always executes."); }

You can define custom error classes to represent specific error types.

class CustomError extends Error { constructor(message: string) { super(message); this.name = "CustomError"; } } try { throw new CustomError("This is a custom error."); } catch (error) { if (error instanceof CustomError) { console.error("Caught a custom error:", error.message); } else { console.error("Caught an unknown error."); } }

You can use throw to enforce validation logic.

function divide(a: number, b: number): number { if (b === 0) { throw new Error("Division by zero is not allowed."); } return a / b; } try { console.log(divide(10, 2)); // Output: 5 console.log(divide(10, 0)); // Throws an error } catch (error) { console.error("Error:", error.message); }

Best Practices:

  1. Use try/catch sparingly for exceptional cases, not for regular control flow.
  2. Always clean up resources in the finally block if needed.
  3. Use custom error classes for better error categorization and debugging.