Ansible is much more of an imperative system, sort of "executable YAML". You define a series of tasks in a YAML file. There are predefined tasks for standard things that you need to do when configuring a system, e.g. creating a directory or generating a config file by merging Ansible configuration variables with template. You can and should make these tasks idempotent, but as the system gets more complex, it becomes difficult and runtime can be slow as it compares tasks one by one to the running system.
Both systems suffer somewhat from difficulty in writing code. The fundamental task is to transform configuration variables and templates into running resources. To do that, you need loops, if/then/else logic, etc. Ansible has some constructs, but it is basically string manipulation, with a backdoor of being able to write modules in python. Terraform has a better syntax to define resources. Logic is generally things like ternary operator and list comprehensions. Terraform 0.12 improved this tremendously, but it is still somewhat weak. Ansible has a bit better management of config variables. Terraform tends to make you serialize things through environment vars, and it's awkward to define structure sometimes. Both would benefit greatly from first class functions and programming logic, even as they are "functional", just transforming data.
I love them both, and I hate them both. Terraform is best for provisioning complex infrastructure. Ansible is great for setting up instances, and it's easy for everyone to understand, dev and ops. Here is a complete example of deploying a complex, full-featured app to AWS using Terraform and Ansible: https://github.com/cogini/multi-env-deploy
I feel like we are suffering through a period where the tools are immature. People are focusing on syntax, but we are missing fundamental parts of the way the system should work. https://www.cogini.com/blog/is-it-time-for-lisp-in-devops/
The exact same thing is going on in the Kubernetes world. Back in the .com days, we would laugh at the "HTML programmers", but now we are "YAML programmers".
There are a couple of fundamental ways of managing the new cloud systems, all of which are better or worse depending on what you are doing. There are declarative systems like Terraform or CloudFormation. There is imperative with tasks, like Ansible. There are things that talk directly to the API like boto. There are tools like Pulumi which take a library approach in a general purpose programming language. Dockerfiles are crying out for higher level solutions, which are being developed. Ultimately I like the approach of a dedicated syntax like Terraform, but with more programming capability, or Pulumi.