Control Flow

Change Topic
  • Conditional statements
  • Ternary Operator or Select
  • Switch / Case
  • Loop Over - Iterables
  • Loop Conditional Exit
  • Co-Routine - Yield
  • Exceptions

Go Control-Flow

TypeScript Control-Flow


Conditional

import "fmt" func checkNumber(num int) string { if num > 0 { return "Positive" } else if num < 0 { return "Negative" } else { return "Zero" } } func main() { fmt.Println(checkNumber(5)) // Positive fmt.Println(checkNumber(-3)) // Negative fmt.Println(checkNumber(0)) // Zero }

Conditional

function checkNumber(num: number): string { if (num > 0) { return "Positive"; } else if (num < 0) { return "Negative"; } else { return "Zero"; } }

Ternary and Select Expressions

Go does not have a ternary operator (? :). Instead, we use if expressions:

import "fmt" func main() { num := 5 var result string if num > 0 { result = "Positive" } else if num < 0 { result = "Negative" } else { result = "Zero" } fmt.Println(result) }

Ternary and Select Expressions

Ternary operator:

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

Loop Over

Go supports multiple loop constructs.

Basic for loop

func main() { for i := 0; i < 5; i++ { fmt.Println(i) } }

Range-based iteration (for range)

func main() { numbers := []int{1, 2, 3} for _, num := range numbers { fmt.Println(num) } }

Iterating Over a Map (Equivalent of for...in)

func main() { user := map[string]string{"id": "1", "name": "Alice"} for key, value := range user { fmt.Printf("%s: %s\n", key, value) } }

Loop Over

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

Go allows breaking out of loops based on conditions and also supports do...while-like behavior using for.

Basic for with condition (Equivalent of do...while)

func main() { i := 0 for { fmt.Println(i) i++ if i >= 5 { break // Exit loop } } }

Breaking out of a loop

func main() { for i := 0; i < 10; i++ { if i == 5 { break // Exit loop when i equals 5 } fmt.Println(i) } }

Using continue to skip iterations

func main() { for i := 0; i < 10; i++ { if i%2 == 0 { continue // Skip even numbers } fmt.Println(i) } }

Loop Conditional Exit

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); }

Coroutine and Yield

Go does not support native coroutines or yield, but goroutines and channels provide similar behavior for asynchronous execution.

Using a Goroutine for Lazy Evaluation

func generator(ch chan int) { for i := 1; i <= 3; i++ { ch <- i // Send value to channel time.Sleep(time.Second) } close(ch) // Close the channel when done } func main() { ch := make(chan int) go generator(ch) for num := range ch { // Reads from the channel until closed fmt.Println(num) } }

Infinite Counter with Goroutines (Lazy Iteration)

import ( "fmt" "time" ) func counter(ch chan int) { i := 0 for { ch <- i // Send value i++ time.Sleep(time.Second) // Simulate work } } func main() { ch := make(chan int) go counter(ch) fmt.Println(<-ch) // 0 fmt.Println(<-ch) // 1 fmt.Println(<-ch) // 2 }

Goroutines + Channels provide a coroutine-like mechanism in Go, allowing controlled iteration with lazy execution.


Coroutine and Yield

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

Generator function with yield:

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.


Error Handling in Go

Go does not have exceptions like many other programming languages (e.g., try/catch blocks). Instead, Go uses error values to handle errors explicitly. This approach encourages developers to handle errors at every step, making the code more predictable and robust. Basic Error Handling:

In Go, functions often return an error as the second return value to indicate if something went wrong.

import ( "errors" "fmt" ) func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } func main() { result, err := divide(10, 0) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Result:", result) } }

Using panic and recover: - Go provides panic and recover for handling unexpected errors. However, these are typically used for unrecoverable errors or debugging purposes, not for general error handling.

  • panic: Stops the normal execution of a program and begins unwinding the stack.
  • recover: Allows you to regain control of a panicking program.
import "fmt" func riskyFunction() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() panic("Something went wrong!") } func main() { fmt.Println("Before risky function") riskyFunction() fmt.Println("After risky function") }

Best Practices for Error Handling in Go: -

  1. Return Errors Explicitly:

    • Always return errors as part of the function's return values.
    • Check for errors immediately after calling a function.
  2. Use panic for Truly Exceptional Cases:

    • Reserve panic for situations where the program cannot continue (e.g., corrupted state, missing critical resources).
  3. Wrap Errors for Context:

    • Use the fmt.Errorf function to add context to errors.
import ( "fmt" "os" ) func readFile(filename string) error { _, err := os.Open(filename) if err != nil { return fmt.Errorf("failed to open file %s: %w", filename, err) } return nil } func main() { err := readFile("nonexistent.txt") if err != nil { fmt.Println("Error:", err) } }