> I've heard of dictionary lookups but I don't really understand what they are so if anyone thinks that they maybe relevant and could show me how to implement it that would be great!

Here you go!

Use Dictionaries!
=================
The entire `if/elif/else` if yelling, if not screaming, to be put into a dictionary. This is a lot faster than your implementation because it's a simple key lookup, instead of multiple logical checks to test the user input.

Here is your `test` function (now named `favorite_youtuber`):
```
def favorite_youtuber():

    youtube_response = {
        'dr. phil': 'You are an awesome lad!',
        'james charles': 'Errmm. Ok...',
        'bloamz': 'Ok then.',
        'ali a': 'I mean. Thats old but ok...',
        'jacksepticeye': 'Thats kinda cool',
        'will smith': 'Thats diferent. I rate that.',
        'jack black': 'You have good taste.',
        'jack white': 'I like him as well.',
        'dr. mike': 'So you like learning then!',
        'morgz': 'I mean just leave now.'
    }
    # Since some youtubers in the list below are not included in the dictionary, I left the list. #
    my_favorite_youtubers = ['Dr. Phil', 'Mr. Beast', 'T-Series', 'PewDiePie', '5 Minute Crafts', 'The Ellen Show']

    youtuber = input('Enter your favourite youtuber:  ').lower()

    if youtuber in youtube_response.keys():
        print(youtube_response[youtuber])
    else:
        print('I dont know that one. Ill check them out.')

    print(f'My favourite youtuber is {random.choice(my_favorite_youtubers)}!')
```

A dictionary works by utilizing `keys` and `values`, like so:
```
my_dict = {
    "key": "value of any type",
    ...
}
```
In this case, the `key` is the name of the youtuber that the user inputs, and the `value` is the response. This prevents you from having to have multiple print statements depending on what the user inputs. Now, all you have to do is make sure that the youtuber entered by the user is included in the dictionary's keys, utilizing this line:

```
if youtuber in youtube_response.keys():
```

Format Your Strings!
====================
The era of `my_string = a + " " + b` is over. You can now format your strings to include your variables directly in them! Take a look:

```
print(f'My favourite youtuber is {random.choice(my_favorite_youtubers)}!')
```

Essentially, the value of the code within `{}` is placed in that position in the string.

Another option is to use [`.format()`][1], which is a method called on a string. Take a look:

```
print('My favourite youtuber is {}'.format(random.choice(my_favorite_youtubers)))
```

They both do the same thing. It's use to you which one you want to use.

`.lower()` utilization
======================
Instead of
```
maybe = input("Y/N ")
maybe = maybe.lower()
```
do this
```
maybe = input("Y/N ").lower()
```
Since `input()` returns a string, `.lower()` applies to that string. This prevents you from having to write that extra line, and it makes your code a little nicer.

`\n`
====
Instead of
```
print("That is not a valid option")
print("")
```
do this
```
print("That is not a valid option.\n")
```
It adds a newline character at the end of the string, doing exactly what you're doing but in a nicer way.

Repetitive User Input
=====================
Now let's talk about your `try_again` function.

There's a lot to break down here. I find it easier to show you my improved version of your code, and walking you through what I did. Have a look:
```
def run_app():
    while True:
        favorite_youtuber()
        again = input("Play again? (Y/N)")
        while again.lower() not in "yn":
            print("Please enter Y/N!")
            again = input("Play again? (Y/N)")
        if again == "n":
            break
    print("Thank you for using this app!")
```
It's fairly self explanatory. The one thing I want to talk about is the nested `while` loop.

Instead of checking if something is within a tuple `("y", "n")`, you can check if something is within a string `"yn"`. It's easier to understand this way. The while loop keeps asking for input until the user enters a "y" or a "n". This is easier than having nested `while True:` loops, as those can get very messy very fast.

Since you only want to see if they *don't* want to keep playing, you only need to check for the existence of an "n". Then, it's a simple `break` statement to print out the final goodbye.

Main Guard
==========
Last thing I'm commenting on.

You should use a main guard when running this program. Why?

Let's say you want to import this module into another program, because you don't want to rewrite all this code in a different file. When you import the module, that spare `try_again` is going to run. That is not what you want. Containing this extra code in a main guard will prevent this from happening. It's a simple `if` statement:
```
if __name__ == "__main__":
    run_app()
```
I renamed your `try_again` to `run_app()`, since that name is more fitting of what the program is doing.

  [1]: https://docs.python.org/3/library/string.html#string.Formatter.format