Closures and variable binding in Ruby - Time & Space Complexity
When working with closures in Ruby, it's important to understand how the program runs as the input grows.
We want to see how the number of steps changes when closures capture variables and run repeatedly.
Analyze the time complexity of the following code snippet.
def make_multipliers(n)
multipliers = []
n.times do |i|
multipliers << ->(x) { x * i }
end
multipliers
end
multipliers = make_multipliers(5)
multipliers.each { |m| puts m.call(10) }
This code creates a list of closures that multiply by numbers from 0 to n-1, then calls each closure with 10.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping n times to create closures and then looping n times to call them.
- How many times: Each loop runs n times, so total operations grow with n.
As n grows, the number of closures created and calls made both increase linearly.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 (10 creates + 10 calls) |
| 100 | About 200 (100 creates + 100 calls) |
| 1000 | About 2000 (1000 creates + 1000 calls) |
Pattern observation: The total steps grow roughly twice as fast as n, which is still a straight line growth.
Time Complexity: O(n)
This means the time to run grows directly in proportion to the number of closures created and called.
[X] Wrong: "Closures cause the code to run slower exponentially because they capture variables."
[OK] Correct: Creating closures and calling them still happens in a simple loop, so time grows linearly, not exponentially.
Understanding how closures work with variable binding and their time cost helps you explain your code clearly and reason about performance in real projects.
"What if we called each closure multiple times inside another loop? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of closure
A closure is a function or block that keeps access to variables from where it was created, even if called later.Step 2: Match the definition to options
A function that remembers variables from its creation context correctly describes this behavior. Other options describe unrelated concepts.Final Answer:
A function that remembers variables from its creation context -> Option CQuick Check:
Closure = function remembering variables [OK]
- Thinking closures are just methods
- Confusing closures with classes
- Believing closures change variable values automatically
Solution
Step 1: Identify Ruby lambda syntax
In Ruby, lambdas can be created withlambda { |args| ... }or->(args) { ... }.Step 2: Check each option
closure = lambda { |x| x * 2 } useslambda { |x| x * 2 }, which is valid. closure = def(x) x * 2 end uses invalid syntax for a lambda. closure = function(x) { x * 2 } uses JavaScript-like syntax. closure = -> x x * 2 misses parentheses in the arrow syntax, which is invalid.Final Answer:
closure = lambda { |x| x * 2 } -> Option AQuick Check:
Correct lambda syntax = closure = lambda { |x| x * 2 } [OK]
- Using JavaScript or Python syntax
- Omitting parentheses in arrow lambdas
- Trying to define lambdas with def keyword
def make_closures
closures = []
3.times do |i|
closures << lambda { i }
end
closures
end
closures = make_closures
closures.each { |c| puts c.call }Solution
Step 1: Understand variable binding in block
Ruby lambdas capture block variables likeiby value in this context, so each lambda remembers its ownivalue.Step 2: Evaluate each lambda call
The loop setsito 0, 1, 2 sequentially. Each lambda captures the currentivalue, so outputs are 0, 1, 2 respectively.Final Answer:
0 1 2 -> Option BQuick Check:
Lambdas capture distinct values per iteration [OK]
- Thinking lambdas capture shared variable (all 2)
- Thinking last value is 3 (off-by-one)
- Expecting error or all zeros
closures = []
3.times do |i|
closures << lambda { j }
j = i
end
closures.each { |c| puts c.call }Solution
Step 1: Identify variable usage inside lambda
The lambda uses variablejbefore it is assigned any value in the block, causing an error when called.Step 2: Understand variable binding timing
Sincejis assigned after lambda creation, the lambda capturesjas nil or undefined at call time, causing an error.Final Answer:
Variable j is used before assignment inside the lambda -> Option AQuick Check:
Use variables after assignment in closures [OK]
- Assuming lambda syntax is wrong
- Thinking times method is invalid
- Ignoring variable initialization order
i even if i changes later?
closures = []
3.times do |i|
closures << lambda { i }
end
closures.each { |c| puts c.call }Solution
Step 1: Understand variable capture in closures
Closures capture variables by reference, so ifichanges later, all lambdas see the last value.Step 2: Use a local variable to capture current value
Assignval = iinside the block, then create lambda usingval. This captures the value at that iteration.Final Answer:
Use a local variable inside the block to capture i:val = i; closures << lambda { val }-> Option DQuick Check:
Capture current value with local variable [OK]
- Thinking proc changes variable capture behavior
- Using global variables which share state
- Calling closures too early inside loop
Ready to prove it?
Pass the test and this pattern moves unknown
