How different languages handle the flow of execution, through conditional statements, loops and errors.
Control Flow
Go Control-Flow
TypeScript Control-Flow
Conditional Statements
Control the flow of execution based on conditions.
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 }
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.
Go does not have a ternary operator (? :
). Instead, we use if
expressions:
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 operator:
const result = num > 0 ? "Positive" : num < 0 ? "Negative" : "Zero";
Switch / Case
A control structure for multi-way branching.
The switch
statement in Go allows you to execute different blocks of code based on the value of an expression. It is more flexible than in many other languages because it does not require explicit break
statements.
func getDayName(day int) 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" } }
You can group multiple cases together if they share the same logic.
func isWeekend(day int) string { switch day { case 0, 6: // Sunday or Saturday return "It's the weekend!" default: return "It's a weekday." } }
In Go, you can use a switch
statement without an expression, which acts like a series of if
/else
conditions.
func checkNumber(num int) string { switch { case num > 0: return "Positive" case num < 0: return "Negative" default: return "Zero" } }
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.
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) } }
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.
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) } }
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.
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.
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.
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: -
-
Return Errors Explicitly:
- Always return errors as part of the function's return values.
- Check for errors immediately after calling a function.
-
Use
panic
for Truly Exceptional Cases:- Reserve
panic
for situations where the program cannot continue (e.g., corrupted state, missing critical resources).
- Reserve
-
Wrap Errors for Context:
- Use the
fmt.Errorf
function to add context to errors.
- Use the
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) } }
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:
- Use
try
/catch
sparingly for exceptional cases, not for regular control flow. - Always clean up resources in the
finally
block if needed. - Use custom error classes for better error categorization and debugging.