# Practical Closure

In some cases, closure can be tricky to predict. Gathered some of these concepts with code implementations to make the closure easy and interesting.

## Definition

A function with a reference from the outer environment creates a closure.

- JS functions can access its lexical scope (outer scope)
- Functions can remember their lexical scope no matter where it is executed

## Example and Tweaking

```js
function outer () {
  var a = 'hello';
  function inner() {
    console.log(a);
  }
  return inner;
}

outer()(); // prints "hello"
```

It is not necessary to define variables above the function definition.

```js
function outer() {
  function inner() {
    console.log(a);
  }
  var a = 'hello'; // define variable after function definition
  return inner;
}

outer()(); // prints 'hello'
```

Instead of using `var`, we can use `let` or `const` instead.

```js
function outer() {
  function inner() {
    console.log(a);
  }
  const a = 'hello';
  return inner;
}

outer()(); // prints 'hello'
```

We can pass a parameter to the parent method and create a closure.

```js
function outer(b) {
  function inner() {
    console.log(b);
  }
  return inner;
}

outer('I am param')(); // prints "I am param"
```

Closure can be formed with multiple hierarchies of methods.

```js
function evenOuter() {
  var val = 'From even outer';
  function outer() {
    function inner() {
      console.log(val);
    }
    return inner;
  }
  return outer;
}

evenOuter()()();
```

Closure took the scoped variable and ignore the upper lexical scope,

```js
var val = 'Global scope';
function outer() {
    function inner() {
      console.log(val);
    }
    var val = 'Outer scope';
    return inner;
  }

outer()(); // prints 'Outer scope'
```

## Advantages of Closures

- Function currying
- Optimized higher-order function
- Memorization
- Data hiding and encapsulation

## Function currying

By function currying, we can create new function for specialized argument and reuse them as needed.

```js
function multiply(a) {
  return function(b) {
    return a * b;
  }
}

// Currying the multiply function
const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15
```

## Higher Order Function

Higher order function takes another function as a parameter.

```js
function repeat(fn, n) {
  return function(x) {
    for (let i = 0; i < n; i++) {
      fn(x);
    }
  }
}

function logWithPrefix(prefix) {
  return function(message) {
    console.log(`${prefix}: ${message}`);
  }
}

const repeatLog = repeat(logWithPrefix('INFO'), 3);
repeatLog('Hello'); // Output: INFO: Hello (three times)
```

## Memorization

Similar to Function currying example.

## Data Hiding and Encapsulation

Consider the following code,

```js
let count = 0;
function increment() {
  count += 1
  console.log(count);
}

increment();
count = 2;
```

Here the major flow is, we can increment the `count` directly without using the `increment` method.

To ensure, always use the `increment` to increment the `count`, we can make use of closure.

```js
function counter() {
  let count = 0;
  function increment() {
    count += 1
    console.log(count);
  }
  return increment;
}

const myIncrement = counter();
myIncrement(); // print 1
myIncrement(); // print 2
```

An interesting fact is, every time we call the `counter` method, it always creates a new session.

```js
function counter() {
  let count = 0;
  function increment() {
    count += 1
    console.log(count);
  }
  return increment;
}

const myIncrement1 = counter();
myIncrement1(); // prints 1
myIncrement1(); // prints 2

const myIncrement2 = counter();
myIncrement2(); // prints 1
myIncrement2(); // prints 2
```

If we need another function, `decrement` then the code snippet is not scalable. In this case, we can make use of `Function Constructor`.

```js
function Counter() {
  let count = 0;
  this.increment = function() {
    count += 1
    console.log(count);
  }
  this.decrement = function () {
    count -= 1;
    console.log(count)
  }
}

const myCounter = new Counter();
myCounter.increment(); // print 1
myCounter.increment(); // print 2
myCounter.decrement(); // print 1
```

## Disadvantages

If  not handled properly,
- Can cause overconsumption of memory
- Memory leaks




