2632. Curry 🔒
Problem Description
This problem asks you to implement a curry function that transforms a regular function into a curried version.
What is currying? Currying is a technique where a function that takes multiple arguments is transformed into a sequence of functions, each taking a single argument (or partial arguments).
Requirements:
- Given a function
fn
, return a curried version of it - The curried function should accept fewer than or equal to the number of parameters as the original function
- If enough arguments are provided, it should return the final result
- If not enough arguments are provided, it should return another function waiting for the remaining arguments
Example behavior:
If you have an original function sum(a, b, c)
that takes 3 arguments, after currying it to csum
, you can call it in multiple ways:
csum(1)(2)(3)
- one argument at a timecsum(1)(2, 3)
- one argument, then two argumentscsum(1, 2)(3)
- two arguments, then one argumentcsum(1, 2, 3)
- all three arguments at once
All these different calling patterns should produce the same result as calling the original sum(1, 2, 3)
.
How the solution works:
- The
curry
function returns a new function calledcurried
that collects arguments - When
curried
is called with some arguments, it checks if the total number of arguments collected so far is enough (>= the original function's parameter count viafn.length
) - If enough arguments are provided, it calls the original function with all the arguments and returns the result
- If not enough arguments are provided, it returns another function that will accept more arguments and combine them with the previously collected ones using the spread operator
...args, ...nextArgs
Intuition
The key insight is that we need to accumulate arguments across multiple function calls until we have enough to execute the original function.
Think of it like filling a bucket - each time the curried function is called, we add more water (arguments) to the bucket. Once the bucket is full (we have all required arguments), we can pour it out (execute the original function).
To achieve this behavior, we need:
- A way to remember previously passed arguments - This naturally suggests using closures, where inner functions can access variables from outer scopes
- A way to check if we have enough arguments - JavaScript functions have a
length
property that tells us how many parameters they expect - A recursive structure - If we don't have enough arguments yet, we need to return another function that continues the same pattern
The elegant solution uses the spread operator (...args
) to collect arguments and a recursive approach where curried
calls itself. When curried
is first called with some arguments, those arguments are captured in its scope. If we need more arguments, we return a new arrow function that:
- Accepts additional arguments (
...nextArgs
) - Combines them with the previously collected ones (
...args, ...nextArgs
) - Recursively calls
curried
with the combined arguments
This creates a chain of functions, each holding onto its piece of the argument list, until we finally have enough to call the original function. The beauty of this approach is that it handles any combination of partial applications - whether you pass arguments one at a time or in groups, the recursive structure naturally accumulates them until the requirement is met.
Solution Approach
Let's walk through the implementation step by step:
1. Creating the curry wrapper function:
function curry(fn: Function): Function {
// Returns a new function that will handle the currying logic
}
2. Implementing the curried function with argument collection:
return function curried(...args) {
// ...args collects all arguments passed in the current call
}
The curried
function uses the rest parameter syntax ...args
to collect any number of arguments passed to it. This allows flexible partial application.
3. Checking if we have enough arguments:
if (args.length >= fn.length) { return fn(...args); }
fn.length
gives us the number of parameters the original function expects- If the current argument count meets or exceeds this requirement, we execute the original function with all collected arguments using the spread operator
...args
- This handles both the case where all arguments are provided at once and when we've accumulated enough through multiple calls
4. Handling partial application:
return (...nextArgs) => curried(...args, ...nextArgs);
When we don't have enough arguments yet:
- We return a new arrow function that accepts additional arguments
...nextArgs
- This arrow function calls
curried
recursively with the combined arguments from both the current call (args
) and the new call (nextArgs
) - The spread operators merge the argument arrays:
...args, ...nextArgs
creates a new array containing all arguments collected so far
Key patterns used:
- Closure: The returned arrow function captures
args
from its parent scope, preserving the arguments between calls - Recursion:
curried
calls itself with accumulated arguments, creating a chain of function calls - Rest/Spread operators: Used for flexible argument handling and array concatenation
Example execution flow for csum(1)(2)(3)
where sum
takes 3 parameters:
csum(1)
:args = [1]
, length < 3, returns a function(2)
: Previousargs = [1]
, newnextArgs = [2]
, callscurried(1, 2)
, still < 3, returns another function(3)
: Previousargs = [1, 2]
, newnextArgs = [3]
, callscurried(1, 2, 3)
, length = 3, executesfn(1, 2, 3)
and returns the result
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the curry function works. We'll use a simple multiply function that takes 3 arguments:
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
Now let's trace through the execution of curriedMultiply(2)(3)(4)
:
Step 1: First call - curriedMultiply(2)
- The
curried
function is called withargs = [2]
- Check:
args.length (1) >= fn.length (3)
? No, we need more arguments - Returns:
(...nextArgs) => curried(...[2], ...nextArgs)
- This returned function remembers
[2]
in its closure
Step 2: Second call - (3)
on the returned function
- The arrow function from Step 1 is called with
nextArgs = [3]
- It executes:
curried(...[2], ...[3])
which becomescurried(2, 3)
- Now
curried
is called withargs = [2, 3]
- Check:
args.length (2) >= fn.length (3)
? No, still need one more - Returns:
(...nextArgs) => curried(...[2, 3], ...nextArgs)
- This new function remembers
[2, 3]
in its closure
Step 3: Third call - (4)
on the returned function
- The arrow function from Step 2 is called with
nextArgs = [4]
- It executes:
curried(...[2, 3], ...[4])
which becomescurried(2, 3, 4)
- Now
curried
is called withargs = [2, 3, 4]
- Check:
args.length (3) >= fn.length (3)
? Yes! We have enough arguments - Executes:
multiply(2, 3, 4)
and returns24
Alternative calling pattern - curriedMultiply(2, 3)(4)
:
Step 1: curriedMultiply(2, 3)
curried
is called withargs = [2, 3]
- Check:
args.length (2) >= fn.length (3)
? No - Returns:
(...nextArgs) => curried(...[2, 3], ...nextArgs)
Step 2: (4)
on the returned function
- The arrow function is called with
nextArgs = [4]
- Executes:
curried(2, 3, 4)
- Check:
args.length (3) >= fn.length (3)
? Yes! - Returns:
multiply(2, 3, 4)
=24
The beauty of this solution is that the same recursive pattern handles any combination of partial applications, accumulating arguments until the function can be executed.
Solution Implementation
1def curry(fn):
2 """
3 Creates a curried version of the provided function.
4 Currying transforms a function with multiple arguments into a sequence of functions,
5 each taking a single argument or partial arguments.
6
7 Args:
8 fn: The function to be curried
9
10 Returns:
11 A curried version of the input function
12 """
13
14 def curried(*args):
15 """
16 The curried function that accumulates arguments until all required arguments are provided
17
18 Args:
19 *args: Initial arguments passed to the curried function
20
21 Returns:
22 Either the result of the original function (if enough arguments) or another curried function
23 """
24 # Check if we have collected enough arguments to call the original function
25 # In Python, we use fn.__code__.co_argcount to get the number of required arguments
26 if len(args) >= fn.__code__.co_argcount:
27 # All required arguments are available, execute the original function
28 return fn(*args)
29
30 # Not enough arguments yet, return a new function that collects more arguments
31 def collect_more_args(*next_args):
32 # Recursively call curried with accumulated arguments
33 return curried(*args, *next_args)
34
35 return collect_more_args
36
37 return curried
38
39
40# Example usage:
41# def sum(a, b):
42# return a + b
43#
44# csum = curry(sum)
45# result = csum(1)(2) # Returns 3
46
1import java.util.function.Function;
2import java.util.ArrayList;
3import java.util.List;
4
5/**
6 * Utility class for creating curried versions of functions.
7 * Currying transforms a function with multiple arguments into a sequence of functions,
8 * each taking a single argument or partial arguments.
9 */
10public class CurryUtil {
11
12 /**
13 * Creates a curried version of the provided function.
14 * Since Java doesn't support variable arity in the same way as JavaScript,
15 * this implementation works with a specific function signature.
16 *
17 * @param fn The function to be curried (BiFunction-like with Object array input)
18 * @param requiredArgsCount The number of arguments required by the original function
19 * @return A curried version of the input function
20 */
21 public static Function<Object[], Object> curry(Function<Object[], Object> fn, int requiredArgsCount) {
22 /**
23 * The curried function that accumulates arguments until all required arguments are provided
24 * Returns either the result of the original function (if enough arguments) or another curried function
25 */
26 return new Function<Object[], Object>() {
27 @Override
28 public Object apply(Object[] args) {
29 return curried(args);
30 }
31
32 /**
33 * Recursive helper method that accumulates arguments
34 * @param args Current accumulated arguments
35 * @return Either the result or a new function waiting for more arguments
36 */
37 private Object curried(Object[] args) {
38 // Check if we have collected enough arguments to call the original function
39 if (args.length >= requiredArgsCount) {
40 // All required arguments are available, execute the original function
41 return fn.apply(args);
42 }
43
44 // Not enough arguments yet, return a new function that collects more arguments
45 return (Function<Object[], Object>) (Object[] nextArgs) -> {
46 // Combine current arguments with new arguments
47 Object[] combinedArgs = new Object[args.length + nextArgs.length];
48 System.arraycopy(args, 0, combinedArgs, 0, args.length);
49 System.arraycopy(nextArgs, 0, combinedArgs, args.length, nextArgs.length);
50
51 // Recursively call curried with accumulated arguments
52 return curried(combinedArgs);
53 };
54 }
55 };
56 }
57
58 /**
59 * Example usage:
60 * Function<Object[], Object> sum = args -> (Integer)args[0] + (Integer)args[1];
61 * Function<Object[], Object> curriedSum = curry(sum, 2);
62 * // Can be called as: ((Function<Object[], Object>)curriedSum.apply(new Object[]{1})).apply(new Object[]{2})
63 * // Returns: 3
64 */
65}
66
1#include <functional>
2#include <tuple>
3#include <utility>
4
5/**
6 * Creates a curried version of the provided function.
7 * Currying transforms a function with multiple arguments into a sequence of functions,
8 * each taking a single argument or partial arguments.
9 *
10 * @tparam Func - The type of function to be curried
11 * @tparam Args - Accumulated argument types
12 */
13template<typename Func, typename... Args>
14class Curried {
15private:
16 Func fn; // Original function to be curried
17 std::tuple<Args...> args; // Tuple storing accumulated arguments
18
19public:
20 /**
21 * Constructor for the curried function wrapper
22 * @param f - The function to curry
23 * @param a - Accumulated arguments so far
24 */
25 Curried(Func f, Args... a) : fn(f), args(a...) {}
26
27 /**
28 * Function call operator that handles new arguments
29 * @tparam NewArgs - Types of new arguments being passed
30 * @param newArgs - New arguments to accumulate or use for execution
31 * @returns Either the result of the original function or another curried function
32 */
33 template<typename... NewArgs>
34 auto operator()(NewArgs... newArgs) {
35 // Combine existing arguments with new arguments
36 auto allArgs = std::tuple_cat(args, std::make_tuple(newArgs...));
37
38 // Check if we have enough arguments to call the original function
39 constexpr size_t totalArgs = sizeof...(Args) + sizeof...(NewArgs);
40
41 // Helper to determine if function can be called with current arguments
42 if constexpr (std::is_invocable_v<Func, Args..., NewArgs...>) {
43 // All required arguments are available, execute the original function
44 return std::apply(fn, allArgs);
45 } else {
46 // Not enough arguments yet, return a new curried function with accumulated arguments
47 return Curried<Func, Args..., NewArgs...>(fn, std::get<Args>(args)..., newArgs...);
48 }
49 }
50};
51
52/**
53 * Creates a curried version of the provided function
54 * @tparam Func - The type of function to be curried
55 * @param fn - The function to be curried
56 * @returns A curried version of the input function
57 */
58template<typename Func>
59auto curry(Func fn) {
60 // Return initial curried function with no accumulated arguments
61 return Curried<Func>(fn);
62}
63
64/**
65 * Example usage:
66 * auto sum = [](int a, int b) { return a + b; };
67 * auto csum = curry(sum);
68 * int result = csum(1)(2); // 3
69 */
70
1/**
2 * Creates a curried version of the provided function.
3 * Currying transforms a function with multiple arguments into a sequence of functions,
4 * each taking a single argument or partial arguments.
5 *
6 * @param fn - The function to be curried
7 * @returns A curried version of the input function
8 */
9function curry(fn: Function): Function {
10 /**
11 * The curried function that accumulates arguments until all required arguments are provided
12 * @param args - Initial arguments passed to the curried function
13 * @returns Either the result of the original function (if enough arguments) or another curried function
14 */
15 return function curried(...args: any[]): any {
16 // Check if we have collected enough arguments to call the original function
17 if (args.length >= fn.length) {
18 // All required arguments are available, execute the original function
19 return fn(...args);
20 }
21
22 // Not enough arguments yet, return a new function that collects more arguments
23 return (...nextArgs: any[]) => {
24 // Recursively call curried with accumulated arguments
25 return curried(...args, ...nextArgs);
26 };
27 };
28}
29
30/**
31 * Example usage:
32 * function sum(a, b) { return a + b; }
33 * const csum = curry(sum);
34 * csum(1)(2) // 3
35 */
36
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of currying calls needed before the function executes.
- Each call to the curried function involves:
- Checking
args.length >= fn.length
:O(1)
- Spreading and combining arguments:
O(k)
wherek
is the number of accumulated arguments - Since arguments accumulate across calls, in the worst case with
n
calls, the total time isO(1) + O(2) + ... + O(n) = O(n²/2) = O(n²)
for spreading operations across all calls
- Checking
- However, in practical usage where each call typically adds a fixed small number of arguments, the amortized time complexity is
O(n)
Space Complexity: O(n * m)
where n
is the number of partial application calls and m
is the average number of arguments per call.
- Each returned function closure maintains:
- A reference to the accumulated arguments array:
O(k)
wherek
is the number of accumulated arguments so far - The closure scope with references to the outer function
- A reference to the accumulated arguments array:
- In the call stack during nested calls:
O(n)
for the depth of recursive closures - Total space used for storing all accumulated arguments across the chain:
O(n * m)
- The space is released once the final function executes and returns
Common Pitfalls
1. Functions with Default Parameters or Variable Arguments
The Problem:
The curry implementation relies on fn.length
(JavaScript) or fn.__code__.co_argcount
(Python) to determine when enough arguments have been collected. However, these properties don't account for:
- Functions with default parameters (they're not counted in the length)
- Functions using rest parameters (
...args
in JS or*args
in Python) - Functions with keyword-only arguments in Python
Example of the issue:
// JavaScript
function greet(name, greeting = "Hello") {
return `${greeting}, ${name}!`;
}
console.log(greet.length); // Returns 1, not 2!
const curriedGreet = curry(greet);
curriedGreet("Alice"); // Executes immediately with undefined greeting
curriedGreet("Alice")("Hi"); // Never executes, returns a function instead
# Python
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# greet.__code__.co_argcount returns 2, but only 1 is required
curriedGreet = curry(greet)
curriedGreet("Alice") # Waits for second argument unnecessarily
Solution: For functions with default parameters, you can:
- Create a wrapper that explicitly specifies the expected argument count:
function curry(fn, arity = fn.length) {
return function curried(...args) {
if (args.length >= arity) {
return fn(...args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
// Usage with explicit arity
const curriedGreet = curry(greet, 1); // Specify it needs minimum 1 argument
- Or handle optional parameters separately by checking if the function can be executed:
function curry(fn) {
return function curried(...args) {
// Try to execute if we have at least the minimum required arguments
if (args.length >= fn.length) {
return fn(...args);
}
// For safety, you could also add a maximum argument check
if (args.length > fn.length + 5) { // arbitrary buffer for optional params
return fn(...args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
2. Methods and Context (this
binding) Loss
The Problem:
When currying object methods, the this
context gets lost because the curried function creates new function scopes.
Example of the issue:
const calculator = {
base: 10,
add: function(a, b) {
return this.base + a + b;
}
};
const curriedAdd = curry(calculator.add);
curriedAdd(5)(3); // Error or unexpected result - 'this' is undefined or global
Solution: Preserve the context by binding it or accepting it as a parameter:
function curry(fn, context = null) {
return function curried(...args) {
if (args.length >= fn.length) {
// Apply with preserved context
return context ? fn.apply(context, args) : fn(...args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
// Usage
const curriedAdd = curry(calculator.add, calculator);
// Or use bind before currying
const curriedAdd = curry(calculator.add.bind(calculator));
3. Functions with No Parameters
The Problem: Functions that take zero parameters will immediately execute when curried, which might not be the expected behavior.
Example of the issue:
function getCurrentTime() {
return new Date().toISOString();
}
const curriedTime = curry(getCurrentTime);
// This executes immediately and returns the time, not a function
const result = curriedTime(); // 'result' is a timestamp, not callable
Solution: Handle zero-parameter functions as a special case:
function curry(fn) {
// Special case for zero-parameter functions
if (fn.length === 0) {
return fn; // Return the function as-is, or wrap it differently
}
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
These pitfalls highlight the importance of understanding the specific requirements and edge cases of the functions you're currying, and potentially creating more sophisticated curry implementations for production use.
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!