Implementations and analysis of my python login system (~80 lines)
When entering passwords, you may want to opt for using the getpass library. It allows you (if the console supports it) to hide passwords as they're being typed. You'd use getpass.getpass just as you are using input to ask for the password. The only difference is, the password won't show in the console as it's typed.

You're using recursion in get_password in order to ask the user again. This isn't a good idea. If the user fails too many times, your program can crash. Just use a loop:
def get_password():
    while True:
        password = input('Enter password: ')
        if len(password) &gt;= 8:
            confirm_password = input('Confirm password: ')
            if password == confirm_password:
                credentials.append(password)
                return
            else:
                print('Password does not match with confirmation\n\n')
        else:
            print('Password too short (min 8 characters)\n\n')

I'm looping until correct input is entered, then I'm just returning from within the infinite loop.
And the same goes for the rest of your functions. Recursion is great when used properly, but it carries more problems than most other looping methods. Use it in cases where it's appropriate (like when the problem itself is recursive), not just for general looping.

add_credentials would be safer if it used a with block to close the file for you:
def add_credentials_to_database():
    credentials_file = os.path.join(database_path, f'{credentials[0]}.txt')
    with open(credentials_file, 'w') as file:
        file.write(credentials[1])
    print('Account created successfully\n\n')
    initialize()

Now, even if an error were to happen half-way through the function, there's a greater chance that the file would be closed. Making a practice of using with also prevents you from forgetting to add a call to file.close().

At the top you have:
credentials = []
database_path = &quot;C:\\Users\\filip\\PycharmProjects\\database&quot;
database = os.listdir(database_path)

A few things to note:

credentials shouldn't be a global here. Global mutable states (which credentials is) tend to complicate code and make it harder to understand. In this set up, I would probably make credentials a local variable of initialize, have get_password and the like to return the data instead of adding it to the list directly, and manually pass that data to add_credentials_to_database.

database_path is a module-level constant, and as such should be in upper-case like DATABASE_PATH according to PEP8.

Arguably database is also (effectively) constant, so it should also be in upper-case.



In initialize, I would get rid of the int conversion of choose_option. All the call to int is doing is allowing for a ValueError to be raised if the user enters in a dumb menu option like &quot;a&quot;. Just use the raw string value and compare against it:
choose_option = input('0. Exit\n1. Login\n2. Sign up\n\nOption: ')
if choose_option == &quot;1&quot;:
    . . .

If you actually needed it as an integer to do math on it or something, it would be different. It's simply an &quot;indicator value&quot; though, so the type of choose_option doesn't really matter, and keeping it as a string has less problems.


And my favorite projects that I still do whenever I learn a new language are:

A naïve, bruteforce prime number finder (and then maybe a sieve). Fairly basic.
Conway's Game of Life. Harder, and has the potential to turn into a large project.
A Mandelbrot Set image generator. Even harder, and usually becomes a larger project.