DEV Community

Cover image for JavaScript Currying ๐Ÿ”„ with Real-World Use Cases ๐ŸŒ: A Comprehensive Guide ๐Ÿ“š
Burhanuddin S. Tinwala
Burhanuddin S. Tinwala

Posted on

JavaScript Currying ๐Ÿ”„ with Real-World Use Cases ๐ŸŒ: A Comprehensive Guide ๐Ÿ“š

Currying is one of those advanced JavaScript techniques that can transform your code from complex and repetitive to simple, reusable, and elegant. If you havenโ€™t explored currying yet, youโ€™re in for a treat! This post will walk you through real-world use cases of currying and how it can be applied to real scenarios like pricing calculations, logging, role-based authorization, and data transformation pipelines. Ready to supercharge your JavaScript skills? Letโ€™s dive in! ๐ŸŒŸ


๐Ÿค” What Is Currying?

At its core, currying is a functional programming concept that allows you to transform a function that takes multiple arguments into a series of functions that each take a single argument. This is powerful because it allows you to create reusable, modular, and highly flexible functions.

For example:

// Normal function
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5

// Curried function
const addCurried = a => b => a + b;
console.log(addCurried(2)(3)); // 5
Enter fullscreen mode Exit fullscreen mode

Now, you can pass one argument at a time and reuse the functions in different contexts.


๐Ÿ’ก Why Use Currying?

  1. Reusability: Write smaller, reusable functions.
  2. Modularity: Break down complex logic into manageable pieces.
  3. Flexibility: Apply partial arguments to tailor the function to specific use cases.
  4. Readability: Write clean, easy-to-understand code.

Now letโ€™s look at real-world examples where currying can save you time and make your code more maintainable.


๐Ÿ›’ Example 1: Flexible Pricing Calculation for E-commerce

Currying is perfect for scenarios where you need to calculate prices with different conditionsโ€”like discounts, tax rates, and shipping feesโ€”based on user types or locations. It allows you to predefine some arguments and dynamically apply others.

const calculatePrice = basePrice => discount => taxRate => shippingFee => {
    const discountedPrice = basePrice - discount;
    const taxAmount = discountedPrice * taxRate;
    return discountedPrice + taxAmount + shippingFee;
};

// Pre-configure pricing rules for different users or locations
const calculateForUS = calculatePrice(100)(10)(0.08);
const calculateForEU = calculatePrice(100)(20)(0.15);
const calculateForPremium = calculateForUS(5);

// Apply the rules based on user location
console.log(calculateForUS(15)); // 105.2 (US user)
console.log(calculateForEU(10)); // 97 (EU user)
console.log(calculateForPremium(8)); // 105.2 (Premium user)
Enter fullscreen mode Exit fullscreen mode

With this approach, you can easily create different pricing models for various customer segments and regions. Currying allows you to pre-configure certain values, like base price, and dynamically adjust others based on user context.


๐Ÿ“‹ Example 2: Modular Logging System

Currying is also great for building configurable logging systems. By using currying, you can create reusable loggers that handle different log levels (info, error, etc.) and contexts without repeating the code each time.

const createLogger = logLevel => context => message => {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [${logLevel}] [${context}] - ${message}`);
};

// Create loggers for different log levels
const infoLogger = createLogger('INFO');
const errorLogger = createLogger('ERROR');
const debugLogger = createLogger('DEBUG');

// Log messages with different contexts
infoLogger('App started successfully')('Application'); // [INFO] [Application] - App started successfully
errorLogger('Failed to load data')('DataLoader'); // [ERROR] [DataLoader] - Failed to load data
debugLogger('Memory usage is high')('SystemMonitor'); // [DEBUG] [SystemMonitor] - Memory usage is high
Enter fullscreen mode Exit fullscreen mode

By currying the createLogger function, you can easily generate loggers with pre-configured log levels and contexts, making your logging system much more flexible and easier to maintain.


๐Ÿ›ก๏ธ Example 3: Role-Based Authorization Middleware (For Node.js)

In a web application, role-based access control (RBAC) ensures that users with different roles (admin, editor, viewer) have the appropriate access to different resources. Currying allows you to easily create dynamic middleware for various roles.

const authorize = role => action => (req, res, next) => {
    if (req.user.role === role && req.user.permissions.includes(action)) {
        next();
    } else {
        res.status(403).send('Access denied');
    }
};

// Define reusable middleware for roles
const isAdmin = authorize('admin');
const isEditor = authorize('editor');
const isViewer = authorize('viewer');

// Apply middleware to routes
app.get('/admin-dashboard', isAdmin('view'), (req, res) => {
    res.send('Welcome to the Admin Dashboard!');
});

app.post('/edit-content', isEditor('edit'), (req, res) => {
    res.send('Content updated!');
});

app.get('/view-content', isViewer('view'), (req, res) => {
    res.send('Content for viewers');
});
Enter fullscreen mode Exit fullscreen mode

With currying, you can reuse the authorize function and create dynamic access control middleware for different roles and actions. This modular approach ensures your code is clean and maintainable.


๐Ÿงฎ Example 4: Data Transformation Pipeline

Currying is extremely useful when building data processing pipelines, where data undergoes multiple transformations (like filtering, sorting, and adding metadata). You can chain transformation functions together to create a flexible and reusable pipeline.

const transformData = transformations => data => 
    transformations.reduce((result, transform) => transform(result), data);

// Define individual transformations
const addDate = data => data.map(item => ({ ...item, date: new Date() }));
const filterActiveUsers = data => data.filter(item => item.isActive);
const sortByName = data => data.sort((a, b) => a.name.localeCompare(b.name));

// Chain transformations
const processData = transformData([addDate, filterActiveUsers, sortByName]);

// Sample data
const users = [
    { name: 'Alice', isActive: true },
    { name: 'Bob', isActive: false },
    { name: 'Charlie', isActive: true }
];

// Apply transformations
const processedUsers = processData(users);
console.log(processedUsers);
/*
[
    { name: 'Alice', isActive: true, date: '2024-12-06T10:00:00.000Z' },
    { name: 'Charlie', isActive: true, date: '2024-12-06T10:00:00.000Z' }
]
*/
Enter fullscreen mode Exit fullscreen mode

In this scenario, currying allows you to create a dynamic pipeline of transformations that can be easily extended or modified. You can easily add new transformations or change existing ones without rewriting the entire process.


๐ŸŒŸ Why Currying Is a Game-Changer in Real-World Projects

  • Reusable code: Currying enables you to build small, reusable functions that can be adapted to different contexts.
  • Cleaner and more maintainable code: By breaking down complex logic into modular pieces, your code becomes easier to maintain and scale.
  • Flexibility: Currying allows you to create functions that can be customized with partial arguments, making your code much more adaptable to different situations.

โšก Conclusion: Start Using Currying in Your Projects!

Currying is a powerful tool that can help you write more modular, flexible, and reusable JavaScript code. Whether youโ€™re building dynamic pricing calculators, configurable logging systems, or role-based authorization, currying can simplify your logic and make your code easier to maintain.

So, are you ready to start incorporating currying into your own projects? What real-world use cases have you tackled with currying?

Follow me for such tips, share your thoughts and examples in the comments below! ๐Ÿš€

Let's connect LinkedIn

Top comments (0)