-> Functions: functions in python are first class citizen, i.e. they can be passed around as variables. for ex: def hello(): print('hello') greet = hello del hello print(greet()) Here in this example, the hello function is defined and assigned to newly defined variable called greet. Then the function name hello is deleted using del keyword. Here, the "del hello" is called, only the name of the function i.e. hello is deleted instead of deleting the function. This is due to the "greet" variable which is still pointing the function location. Hence, calling the function using greet function name works but calling the function with hello name will throw error as the hello variable is deleted. -> High Order function: function which might return a function or take a function as an argument. for ex: def greet(func): func() def greet2(): def func(): return 5 return func -> decorators: These are used to super boost our function: for ex: #decorator def my_decorator(func): def wrap_func(): print('************') func() print('************') return wrap_func @my_decorator def hello(): print('hellllooooooooooo') def bye(): print('see ya letter') Here, if we our function powerful by using decorators. if we use @ and define the function, during the function call the decorator function will take our function as an argument and inside the decorator function it will call the passed function inside wrapped function. For using decorators we need to write the decorator function in way as written above. But, how it works under the hood? This function defination with decorator is same as @my_decorator def hello(): print('helloooo') var = my_decorator(hello) var() -> decorator with function having arguments: for ex: def my_decorator(func): def wrap_func(*args, **kwargs): func(*args, **kwargs) return wrap_func @my_decorator def hello(greeting, emoji = ':('): print(greeting, emoji) hello('hii')