CrackedRuby CrackedRuby

Overview

Control structures determine the order in which statements execute in a program. Without control structures, code executes sequentially from top to bottom. Control structures break this linear flow by introducing decision points, repetition, and branching based on runtime conditions.

Three primary categories exist: sequential execution (default behavior), selection structures (conditional branching), and iteration structures (loops). Selection structures evaluate conditions and execute different code paths based on boolean results. Iteration structures repeat code blocks until termination conditions are met. Jump statements transfer control to different program locations.

Modern programming languages implement control structures through keywords and syntax that compile to machine-level branch and jump instructions. The processor evaluates conditions and modifies the instruction pointer based on results. Ruby inherits control structure concepts from languages like Smalltalk and Perl, adding syntax designed for readability.

# Sequential execution - no control structures
x = 10
y = 20
z = x + y

# Selection structure - conditional branching
if z > 25
  puts "Sum exceeds threshold"
end

# Iteration structure - repeated execution
3.times do |i|
  puts "Iteration #{i}"
end
# => Iteration 0
# => Iteration 1
# => Iteration 2

Control structures interact with variable scope, exception handling, and method returns. The structure chosen affects code readability, performance characteristics, and maintainability. Different structures suit different problem patterns.

Key Principles

Control structures manipulate program flow through three fundamental mechanisms: evaluation, branching, and repetition. Evaluation determines truth values from expressions. Branching selects execution paths based on evaluation results. Repetition executes code blocks multiple times according to specified conditions.

Conditional Evaluation

Expressions evaluate to boolean values that determine which code path executes. Ruby treats specific values as falsy (false and nil) while all other values are truthy. This differs from languages where zero, empty strings, or empty collections are falsy. Evaluation occurs at runtime and can include method calls, variable comparisons, and complex boolean logic.

Control Flow Transfer

Control flow transfer moves execution from the current statement to a different location in the program. Unconditional transfers occur through statements like return, break, and next. Conditional transfers depend on boolean evaluation results. Each transfer mechanism has specific scope rules and affects the call stack differently.

Branching Logic

Branching creates multiple execution paths within a program. Simple branching involves binary decisions (if/else). Complex branching handles multiple conditions (case/when statements). Branching depth affects code complexity - nested structures create multiple decision points that compound the possible execution paths.

Loop Constructs

Loops repeat code execution while maintaining state across iterations. Pre-test loops evaluate conditions before each iteration. Post-test loops guarantee at least one execution. Infinite loops continue until explicit termination. Each iteration can modify loop variables, access external state, or perform side effects.

Short-Circuit Evaluation

Boolean operators employ short-circuit evaluation where the second operand is not evaluated if the result can be determined from the first operand alone. The && operator returns the first falsy value or the last value if all are truthy. The || operator returns the first truthy value or the last value if all are falsy.

# Short-circuit with &&
result = nil && expensive_operation()  # expensive_operation never called
# => nil

# Short-circuit with ||
value = user_input || default_value  # default_value used only if user_input is falsy

Guard Clauses

Guard clauses handle edge cases and invalid conditions at the start of methods or blocks. Early returns prevent deep nesting and improve readability by eliminating invalid states before main logic executes. Guard clauses make the expected execution path more prominent by handling exceptional cases first.

Expression-Oriented Control

Ruby treats control structures as expressions that return values. Every control structure evaluates to the last expression executed within it. This enables assignment from conditionals, inline ternary operations, and functional programming patterns where control flow produces values rather than just causing side effects.

# Control structure as expression
status = if score > 90
  "excellent"
elsif score > 70
  "good"
else
  "needs improvement"
end

Ruby Implementation

Ruby implements control structures with syntax prioritizing readability and expression-oriented programming. The language provides multiple ways to express the same control flow, each with different readability and performance characteristics.

Conditional Statements

The if statement evaluates a condition and executes a code block when true. Ruby supports elsif for additional conditions and else for default cases. Statement modifiers allow single-line conditionals by placing the if keyword after the statement.

# Multi-line if statement
if temperature > 30
  puts "Hot"
elsif temperature > 20
  puts "Warm"
else
  puts "Cold"
end

# Statement modifier - executes only if condition true
puts "Freezing" if temperature < 0

# Unless - inverted conditional logic
unless authenticated?
  redirect_to login_path
end

# Unless statement modifier
process_data unless cache_exists?

The unless keyword provides inverted conditional logic - the block executes when the condition is false. This improves readability when expressing negative conditions but should not be combined with elsif or complex boolean expressions.

Ternary Operator

The ternary operator condition ? true_value : false_value provides inline conditional expressions. Use for simple value selection where both branches return values without side effects.

# Ternary for value assignment
display_name = username.nil? ? "Anonymous" : username

# Avoid ternary for side effects - use regular if instead
# Bad
condition ? execute_action() : execute_other_action()

# Good
if condition
  execute_action()
else
  execute_other_action()
end

Case Statements

Case statements match a value against multiple patterns. Ruby's case/when evaluates each when clause using the === operator, which enables pattern matching beyond simple equality.

# Basic case statement
case status_code
when 200
  "Success"
when 404
  "Not Found"
when 500..599
  "Server Error"
else
  "Unknown Status"
end

# Pattern matching with ranges and types
case input
when String
  input.upcase
when Integer
  input * 2
when 1..10
  "Small number"
else
  nil
end

# Multiple values per when clause
case day
when "Saturday", "Sunday"
  "Weekend"
when "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
  "Weekday"
end

Loop Constructs

Ruby provides several loop mechanisms with different use cases. The while loop continues while a condition remains true. The until loop continues while a condition remains false. The loop method creates an infinite loop terminated by explicit break statements.

# While loop - pre-test condition
counter = 0
while counter < 5
  puts counter
  counter += 1
end

# Until loop - inverted while logic
attempts = 0
until connection_established? || attempts > 3
  establish_connection()
  attempts += 1
end

# Infinite loop with explicit break
loop do
  data = fetch_next_item()
  break if data.nil?
  process(data)
end

Iterator Methods

Ruby emphasizes iterator methods over traditional loops. Methods like each, times, upto, and downto provide functional iteration with block parameters.

# Array iteration
[1, 2, 3].each do |num|
  puts num * 2
end

# Numeric iteration
5.times { |i| puts "Iteration #{i}" }

# Range iteration
1.upto(10) do |n|
  puts n if n.even?
end

# Infinite enumeration with lazy evaluation
(1..Float::INFINITY).lazy.select(&:even?).first(5)
# => [2, 4, 6, 8, 10]

Loop Control Keywords

The break keyword exits the innermost loop immediately. The next keyword skips to the next iteration. The redo keyword restarts the current iteration without re-evaluating the loop condition. The retry keyword restarts the entire loop from the beginning.

# Break - exit loop early
result = [1, 2, 3, 4, 5].each do |num|
  break num if num > 3
end
# => 4

# Next - skip to next iteration
[1, 2, 3, 4, 5].each do |num|
  next if num.even?
  puts num
end
# => Prints 1, 3, 5

# Redo - restart current iteration
attempts = 0
5.times do |i|
  attempts += 1
  puts "Attempt #{attempts} for iteration #{i}"
  redo if attempts < 2 && i == 2
end

Boolean Operators

Ruby provides both logical operators (&&, ||, !) and keyword operators (and, or, not). The symbolic operators have higher precedence than assignment, while keyword operators have lower precedence. This precedence difference creates different evaluation orders.

# Symbolic operators - higher precedence
result = true && false
# result = false

# Keyword operators - lower precedence
result = true and false
# result = true (assignment happens first, then 'and' evaluates)

# Using || for default values
name = user.name || "Unknown"

# Using && for nil-safe method calls
length = string && string.length

Practical Examples

Configuration Validation

Control structures validate configuration objects before system initialization. Multiple conditions check required fields, type constraints, and cross-field dependencies.

class ConfigurationValidator
  def validate(config)
    # Guard clauses for required fields
    return [:error, "Missing database host"] if config[:db_host].nil?
    return [:error, "Missing API key"] if config[:api_key].nil?
    
    # Type validation
    unless config[:port].is_a?(Integer)
      return [:error, "Port must be integer"]
    end
    
    # Range validation
    if config[:port] < 1024 || config[:port] > 65535
      return [:error, "Port out of valid range"]
    end
    
    # Conditional requirement
    if config[:ssl_enabled] && config[:ssl_cert].nil?
      return [:error, "SSL certificate required when SSL enabled"]
    end
    
    [:ok, "Configuration valid"]
  end
end

validator = ConfigurationValidator.new
status, message = validator.validate({
  db_host: "localhost",
  api_key: "secret",
  port: 5432,
  ssl_enabled: true,
  ssl_cert: "/path/to/cert"
})

Data Processing Pipeline

Loops process collections of data with conditional logic determining how each item is handled. Breaking, skipping, and accumulating results based on item properties.

class DataProcessor
  def process_records(records)
    results = []
    error_count = 0
    
    records.each_with_index do |record, index|
      # Skip invalid records
      next unless record[:id] && record[:data]
      
      # Break on too many errors
      if error_count > 10
        puts "Error threshold exceeded at record #{index}"
        break
      end
      
      # Conditional processing based on record type
      processed = case record[:type]
      when :numeric
        process_numeric(record[:data])
      when :text
        process_text(record[:data])
      when :binary
        process_binary(record[:data])
      else
        error_count += 1
        next
      end
      
      # Conditional result storage
      results << processed if processed[:status] == :success
    end
    
    {
      processed: results.size,
      errors: error_count,
      results: results
    }
  end
  
  private
  
  def process_numeric(data)
    value = data.to_f
    { status: :success, value: value, squared: value ** 2 }
  rescue
    { status: :error }
  end
  
  def process_text(data)
    return { status: :error } if data.empty?
    { status: :success, length: data.length, upcase: data.upcase }
  end
  
  def process_binary(data)
    { status: :success, size: data.bytesize }
  end
end

State Machine Implementation

Control structures implement finite state machines where state transitions depend on current state and input events.

class OrderStateMachine
  STATES = [:pending, :processing, :shipped, :delivered, :cancelled]
  
  def initialize
    @state = :pending
    @history = []
  end
  
  def transition(event)
    @history << [@state, event, Time.now]
    
    case @state
    when :pending
      case event
      when :pay
        @state = :processing
      when :cancel
        @state = :cancelled
      else
        raise "Invalid transition from pending: #{event}"
      end
      
    when :processing
      case event
      when :ship
        @state = :shipped
      when :cancel
        # Only allow cancel if processing time under 1 hour
        if processing_time < 3600
          @state = :cancelled
        else
          raise "Cannot cancel after processing began"
        end
      else
        raise "Invalid transition from processing: #{event}"
      end
      
    when :shipped
      if event == :deliver
        @state = :delivered
      else
        raise "Invalid transition from shipped: #{event}"
      end
      
    when :delivered, :cancelled
      raise "Order in terminal state: #{@state}"
    end
    
    @state
  end
  
  def processing_time
    processing_entry = @history.find { |state, _, _| state == :processing }
    return 0 unless processing_entry
    Time.now - processing_entry[2]
  end
  
  attr_reader :state, :history
end

# Usage
order = OrderStateMachine.new
order.transition(:pay)      # => :processing
order.transition(:ship)     # => :shipped
order.transition(:deliver)  # => :delivered

Retry Logic with Backoff

Loops implement retry mechanisms with exponential backoff for transient failures.

class APIClient
  MAX_RETRIES = 5
  BASE_DELAY = 1
  
  def fetch_with_retry(url)
    attempt = 0
    
    loop do
      response = fetch(url)
      
      # Success - return immediately
      return response if response.status == 200
      
      # Client error - don't retry
      if response.status >= 400 && response.status < 500
        raise "Client error: #{response.status}"
      end
      
      # Server error - retry with backoff
      attempt += 1
      
      if attempt >= MAX_RETRIES
        raise "Max retries exceeded for #{url}"
      end
      
      # Exponential backoff
      delay = BASE_DELAY * (2 ** attempt)
      
      # Jitter to prevent thundering herd
      delay += rand(0..delay / 2)
      
      puts "Attempt #{attempt} failed, waiting #{delay}s"
      sleep(delay)
      
      # Retry continues from top of loop
    end
  end
  
  private
  
  def fetch(url)
    # Simulated HTTP request
    # Returns mock response with random status
    OpenStruct.new(status: [200, 500, 503].sample)
  end
end

Common Patterns

Guard Clause Pattern

Guard clauses eliminate nested conditionals by handling edge cases and invalid states at method entry. Early returns make the main execution path more prominent.

# Without guard clauses - nested conditions
def process_order(order)
  if order
    if order.items.any?
      if order.customer
        if order.customer.active?
          # Main logic buried deep in nesting
          calculate_total(order)
        end
      end
    end
  end
end

# With guard clauses - flat structure
def process_order(order)
  return nil if order.nil?
  return nil if order.items.empty?
  return nil if order.customer.nil?
  return nil unless order.customer.active?
  
  # Main logic prominent at method end
  calculate_total(order)
end

Null Object Pattern

Replace conditional nil checks with objects that respond to the same interface but provide safe default behavior.

class NullUser
  def name
    "Guest"
  end
  
  def permissions
    []
  end
  
  def admin?
    false
  end
end

# Without null object
def display_user(user)
  if user
    puts "Welcome, #{user.name}"
    puts "Admin: #{user.admin?}"
  else
    puts "Welcome, Guest"
    puts "Admin: false"
  end
end

# With null object
def display_user(user)
  user ||= NullUser.new
  puts "Welcome, #{user.name}"
  puts "Admin: #{user.admin?}"
end

Strategy Pattern

Replace conditional logic with polymorphic objects implementing a common interface.

# Conditional approach
def calculate_shipping(order, method)
  case method
  when :standard
    order.weight * 0.5
  when :express
    order.weight * 1.5 + 10
  when :overnight
    order.weight * 3.0 + 25
  end
end

# Strategy pattern
class StandardShipping
  def calculate(order)
    order.weight * 0.5
  end
end

class ExpressShipping
  def calculate(order)
    order.weight * 1.5 + 10
  end
end

class OvernightShipping
  def calculate(order)
    order.weight * 3.0 + 25
  end
end

def calculate_shipping(order, strategy)
  strategy.calculate(order)
end

# Usage
order = OpenStruct.new(weight: 10)
cost = calculate_shipping(order, ExpressShipping.new)

Loop Accumulation Pattern

Iterate through collections while accumulating results, often with conditional inclusion.

# Manual accumulation
def extract_valid_emails(users)
  emails = []
  users.each do |user|
    next unless user.email
    next unless user.email.include?('@')
    next if user.suspended?
    emails << user.email
  end
  emails
end

# Using select and map
def extract_valid_emails(users)
  users
    .reject(&:suspended?)
    .map(&:email)
    .compact
    .select { |email| email.include?('@') }
end

State Validation Pattern

Chain multiple validations with early exit on first failure.

class Validator
  def self.validate(data)
    return error("Data is nil") if data.nil?
    return error("Data is empty") if data.empty?
    return error("Missing required field") unless data[:required_field]
    return error("Invalid format") unless valid_format?(data)
    return error("Failed business rule") unless business_rule_check(data)
    
    success(data)
  end
  
  def self.error(message)
    { valid: false, error: message }
  end
  
  def self.success(data)
    { valid: true, data: data }
  end
end

Performance Considerations

Branch Prediction

Modern processors predict branch outcomes to optimize instruction pipelining. Predictable branches execute faster than random branches. Conditional patterns that consistently evaluate the same way benefit from branch prediction.

# Predictable branch - processor learns pattern
1000.times do |i|
  if i.even?  # Alternating pattern
    process_even(i)
  else
    process_odd(i)
  end
end

# Unpredictable branch - random data
random_data.each do |value|
  if meets_complex_condition?(value)  # Random true/false
    process_a(value)
  else
    process_b(value)
  end
end

Short-Circuit Optimization

Place expensive operations last in boolean chains. Short-circuit evaluation prevents unnecessary computation when early conditions determine the result.

# Inefficient - expensive check happens first
if expensive_database_query() && simple_check()
  execute_action()
end

# Efficient - simple check first
if simple_check() && expensive_database_query()
  execute_action()
end

# Multiple conditions ordered by cost
if cached_value() || medium_cost_check() || expensive_operation()
  proceed()
end

Loop Optimization

Extract invariant calculations outside loops to prevent redundant computation. Minimize method calls and object allocations within tight loops.

# Inefficient - repeated calculation
array.each do |item|
  threshold = calculate_threshold()  # Calculated every iteration
  process(item) if item > threshold
end

# Efficient - calculate once
threshold = calculate_threshold()
array.each do |item|
  process(item) if item > threshold
end

# Inefficient - method calls in loop
data.each do |item|
  processor.setup()  # Setup every iteration
  processor.process(item)
  processor.cleanup()
end

# Efficient - setup once
processor.setup()
data.each do |item|
  processor.process(item)
end
processor.cleanup()

Iterator Method Performance

Different iterator methods have different performance characteristics. each typically performs better than for in Ruby. Block allocation impacts performance for tight loops with many iterations.

require 'benchmark'

data = (1..1000000).to_a

Benchmark.bm do |x|
  x.report("each") { data.each { |n| n * 2 } }
  x.report("for")  { for n in data; n * 2; end }
  x.report("while") {
    i = 0
    while i < data.length
      data[i] * 2
      i += 1
    end
  }
end

# Typical results show 'each' and 'while' similar, 'for' slightly slower

Case Statement Optimization

Case statements evaluate conditions sequentially. Place most frequent conditions first to minimize average comparison count.

# Inefficient - rare cases checked first
case status_code
when 418  # I'm a teapot - extremely rare
  handle_teapot()
when 200  # Success - most common
  handle_success()
end

# Efficient - common cases first
case status_code
when 200  # Most common checked first
  handle_success()
when 404
  handle_not_found()
when 418  # Rare case checked last
  handle_teapot()
end

Avoiding Redundant Conditionals

Store conditional results in variables when the same condition is checked multiple times.

# Redundant evaluation
if expensive_check(data)
  log("Check passed")
end

if expensive_check(data)  # Evaluated again
  process(data)
end

# Single evaluation
check_result = expensive_check(data)

if check_result
  log("Check passed")
end

if check_result
  process(data)
end

Common Pitfalls

Assignment in Conditionals

Ruby allows assignment within conditional expressions. Single = performs assignment while == checks equality. Assignment in conditionals returns the assigned value, which is then evaluated for truthiness.

# Accidental assignment
if user = nil  # Assigns nil to user, evaluates to nil (falsy)
  puts "This never executes"
end

# Intended equality check
if user == nil
  puts "User is nil"
end

# Common mistake with input validation
if input = ""  # Assigns empty string, evaluates to "" (truthy)
  puts "This executes even though string is empty"
end

Elsif Misspelling

Ruby uses elsif, not elseif or else if. Misspelling creates syntax errors.

# Wrong
if x > 10
  "high"
else if x > 5  # Syntax error
  "medium"
end

# Correct
if x > 10
  "high"
elsif x > 5
  "medium"
end

Unless with Else

Combining unless with else creates confusing logic. The else clause executes when the condition is true, inverting intuitive understanding.

# Confusing - unless with else
unless user.admin?
  redirect_to :home
else
  show_admin_panel  # Executes when user IS admin
end

# Clear - use if with negation
if user.admin?
  show_admin_panel
else
  redirect_to :home
end

Modifier Statement Scope

Statement modifiers apply only to the statement immediately before them, not to multiple statements.

# Wrong - only puts executes conditionally
puts "Processing"
process_data() if condition  # Only process_data is conditional

# Correct - block for multiple statements
if condition
  puts "Processing"
  process_data()
end

Break Return Values

The break keyword can take an argument that becomes the return value of the loop or iterator method. Forgetting this causes unexpected nil returns.

# Returns nil
result = [1, 2, 3].each do |n|
  break if n > 2
end
# => nil

# Returns the value
result = [1, 2, 3].each do |n|
  break n if n > 2
end
# => 3

Case Statement Fall-Through

Ruby case statements do not fall through. Each when clause exits the case statement when matched. Multiple conditions in one when require comma separation.

# Wrong - syntax error
case value
when 1
when 2  # This is a syntax error, not fall-through
  puts "One or two"
end

# Correct - multiple values in one when
case value
when 1, 2
  puts "One or two"
end

Infinite Loop Creation

Loop conditions that never become false create infinite loops. Common causes include missing increment statements or incorrect condition logic.

# Infinite loop - counter never increments
counter = 0
while counter < 10
  puts counter
  # Missing: counter += 1
end

# Infinite loop - condition never changes
while !file.exists?
  process_data()
  # Missing: code to create file or break condition
end

# Correct - explicit break condition
timeout = Time.now + 30
while !file.exists?
  process_data()
  break if Time.now > timeout
end

Short-Circuit Side Effects

Relying on side effects in short-circuited expressions causes inconsistent behavior when the expression short-circuits.

# Dangerous - second call may not execute
if check_condition() && update_counter()
  proceed()
end

# Safe - execute side effects separately
check = check_condition()
update_counter()
if check
  proceed()
end

Loop Variable Mutation

Modifying loop variables inside the loop body causes unexpected iteration counts or skipped elements.

# Wrong - modifying loop array during iteration
array = [1, 2, 3, 4, 5]
array.each do |item|
  array.delete(item) if item.even?  # Modifies array being iterated
end
# => Results in skipped elements

# Correct - iterate over copy or build new array
array = [1, 2, 3, 4, 5]
array = array.reject(&:even?)  # Creates new array

Reference

Control Structure Keywords

Keyword Purpose Usage Context
if Conditional execution Executes block when condition true
elsif Additional condition Checked when previous conditions false
else Default case Executes when all conditions false
unless Inverted conditional Executes block when condition false
case Pattern matching Matches value against multiple patterns
when Case clause Defines pattern to match in case statement
while Pre-test loop Continues while condition true
until Inverted loop Continues while condition false
for Collection iteration Iterates over enumerable object
loop Infinite loop Continues until explicit break
break Exit loop Immediately exits innermost loop
next Skip iteration Moves to next iteration of loop
redo Restart iteration Restarts current iteration without re-evaluating
retry Restart loop Restarts entire loop from beginning
return Method exit Exits method and returns value

Iterator Methods

Method Description Return Value
each Iterate with block Original collection
times Execute block n times Number of iterations
upto Iterate from value up to limit Starting value
downto Iterate from value down to limit Starting value
loop Infinite iteration nil or break value
each_with_index Iterate with index parameter Original collection
map Transform each element New array of transformed values
select Filter elements New array of matching elements
reject Exclude elements New array of non-matching elements
find Locate first match First matching element or nil
reduce Accumulate result Accumulated value

Boolean Operators

Operator Symbolic Keyword Short-Circuit Precedence
AND && and Yes Higher (symbolic) / Lower (keyword)
OR || or Yes Higher (symbolic) / Lower (keyword)
NOT ! not No Higher (symbolic) / Lower (keyword)

Truthiness Rules

Value Type Truthy Falsy
Boolean true false
Nil nil
Zero 0
Empty String ""
Empty Array []
Empty Hash {}

Comparison Operators

Operator Meaning Example
== Equal value 5 == 5
!= Not equal 5 != 3
< Less than 3 < 5
> Greater than 5 > 3
<= Less than or equal 3 <= 5
>= Greater than or equal 5 >= 3
<=> Comparison (spaceship) 5 <=> 3 returns 1
=== Case equality String === "hello"
=~ Pattern match "hello" =~ /ell/

Statement Modifiers

Statement modifiers allow single-line conditional execution by placing the keyword after the statement.

Modifier Syntax Execution Condition
if statement if condition When condition true
unless statement unless condition When condition false
while begin statement end while condition After first execution while condition true
until begin statement end until condition After first execution until condition true

Loop Control Effects

Keyword Affects Continues Returns
break Innermost loop No Optional value
next Current iteration Yes (next iteration) nil
redo Current iteration Yes (same iteration) nil
retry Entire loop Yes (from start) nil
return Method No Optional value

Case Statement Patterns

# Equality matching
case value
when 1 then "one"
when 2 then "two"
end

# Range matching
case age
when 0..12 then "child"
when 13..19 then "teen"
else "adult"
end

# Class matching
case object
when String then object.upcase
when Integer then object * 2
end

# Regex matching
case input
when /^[A-Z]/ then "starts with capital"
when /\d+/ then "contains digits"
end

# Multiple values
case status
when :pending, :processing then "in progress"
when :complete then "done"
end

Performance Comparison

Structure Use Case Performance Readability
if/elsif Few conditions (2-3) Good Excellent
case/when Many conditions (4+) Better Excellent
Hash lookup Static mappings Best Good
Polymorphism Complex behavior Good Excellent (for objects)
Ternary Simple value selection Best Good (when simple)

Conditional Expression Return Values

All control structures in Ruby return values based on the last evaluated expression.

# if returns last evaluated expression
result = if x > 10
  "high"     # Returned if condition true
else
  "low"      # Returned if condition false
end

# case returns matched branch value
category = case score
when 90..100 then "A"
when 80..89 then "B"
else "F"
end

# Loop with break returns break value
first_match = array.each do |item|
  break item if item > 10
end