Functional programming is a programming paradigm that emphasizes the use of pure functions and immutable data to write code that is more predictable, testable, and easy to reason about. Some of the key techniques used in functional programming include:

  1. Pure functions: A pure function is a function that, given the same inputs, always produces the same output, and has no side effects. This means that it does not modify any external state, and does not interact with any external resources.
  2. Immutable data: In functional programming, data is typically treated as immutable, meaning that it cannot be modified once it's been created. This makes it easier to reason about the state of the program and avoid bugs caused by unexpected changes to data.
  3. Higher-order functions: Higher-order functions are functions that take other functions as arguments and/or return functions as their output. They are a key technique in functional programming, and are used to create more abstract and reusable code.
  4. Recursion: Recursion is a technique where a function calls itself, and is often used to solve problems that can be broken down into smaller sub-problems.
  5. Currying: Currying is a technique where a function returns a new function that takes the next argument, it allows you to reuse functions with different arguments and it's useful in functional programming.
  6. Composition: Composition is a technique where you can combine multiple functions together, to build more complex functionality.
  7. Referential Transparency: A function is referentially transparent if it has no side effects, and it's output only depends on its input. This means that if you call the function with the same arguments multiple times, you will always get the same output.
  8. Lazy evaluation: Lazy evaluation is a technique where a computation is delayed until the result is actually needed. This can be used to improve performance and memory usage.

These techniques, when combined, can be used to create more maintainable and expressive code, by relying on fewer state and mutation, and more on function composition, recursion and higher-order functions to express the logic of the program.