Beginner Coin Flip project
After getting user input, you check it by doing decision == "Yes". This is pretty exact text to expect from the user. I'd at least upper-case their input, and unless you really needed them to be specific, only keep the first letter:

decision = input("You guessed wrong, do you want to continue? Yes or No ")
std_decision = decision[:1].upper()  # [:1] discards all but the first letter

if std_decision == "Y":
   . . .




In coin_flip_game you have

guess_count = 1  #guesses set to 1 so that program considers 1st guess


Then later:

if user_input != random_flip():
    guess_count += 1
    guesses.append(user_input)
    first_try = False
else:
    winner = True


You're only increasing guess_count if the guess was wrong. Regardless of if they were right or wrong though, they still made a guess. I think it makes more sense to add to the guess_count regardless, and start it at 0.

But then you're also only adding to guesses on wrong answers too. If you change it to append to guesses on both right and wrong answers, you'll have this:

guess_count = 0

. . .

guess_count += 1
guesses.append(user_input)

if user_input != random_flip():
    first_try = False

else:
    winner = True


Notice now though that guess_count will always be equal to the length of guesses, so it isn't needed. If you need to find out how many guesses were made, you can just do len(guesses). first_try also isn't necessary. To check if it was their first guess or not, you can do:

if len(guesses) == 0:
    . . .


Or, more idiomatically:

if not guesses:  # "If no(t) guesses (have been made)"
    . . .


I think that whole game loop part should be taken out into it's own function though; beyond what you've already done. I'd have a function that returns a list of guesses made until a correct guess was made. With how you have it set up now, I'm going to disregard cancelling since that will require some broader changes to accommodate, and it's highly unlikely that the user will have to guess more than a few times.

def guess_loop() -&gt; List[str]:
    guesses = []

    while True:  # We're just going to return, so we don't need a condition
        user_input = user_choice()
        guesses.append(user_input)

        if user_input == random_flip():
            return guesses  # Returning guesses means we won

        else:
            print("You guessed wrong. Try again.")

def coin_flip_game():
    print("Lets start! Begin by choosing:")
    guesses = guess_loop()

    if len(guesses) == 1:
        print("Congratulations, you guessed correctly on your first try!")

    else:
        print("Congratulations! After", len(guesses), "you guessed correctly!")
        print("Your guesses were: ", guesses)


The bulky bit was taken out so it can be dealt with as an isolated bit of code. To know the outcome of the game, all I care about (besides cancelling, which could be handled by returning None) is the list of guesses they made until they won. I can get all the information I need from just that. Needing to mix in winner, and cancel flags can suggest that that code should be moved out. By the time you're managing two or three flags to control execution in one block of code, it can become hard to follow.



user_choice can be cleaned up too. Again, you're using a condition to control a loop when I think returning directly from the loop leads to cleaner looking code:

def user_choice():
    while True:
        user_input = input("Heads or Tails?").capitalize()  # Capitalize to give the user some leeway

        if user_input in {"Heads", "Tails"}:
            return user_input

        else:
            print("Invalid input.")




After the above and some small formatting touchups, I'm left with:

import random
from typing import List


def random_flip() -&gt; str:
    return random.choice(["Heads", "Tails"])


def user_choice() -&gt; str:
    while True:
        user_input = input("Heads or Tails?").capitalize()  # Capitalize to give the user some leeway

        if user_input in {"Heads", "Tails"}:
            return user_input

        else:
            print("Invalid input.")


def guess_loop() -&gt; List[str]:
    guesses = []

    while True:  # We're just going to return, so we don't need a condition
        user_input = user_choice()
        guesses.append(user_input)

        if user_input == random_flip():
            return guesses  # Returning guesses means we won

        else:
            print("You guessed wrong. Try again.")


def coin_flip_game() -&gt; None:
    print("Lets start! Begin by choosing:")
    guesses = guess_loop()

    if len(guesses) == 1:
        print("Congratulations, you guessed correctly on your first try!")

    else:
        print("Congratulations! After", len(guesses), "you guessed correctly!")
        print("Your guesses were: ", guesses)

coin_flip_game()


The -&gt; bits are type hints for the return type of the function. I think they're handy to have to make it clearer what type of data is being dealt with.