Intro to Programming: What Are Functions and Methods in Python?

This article discusses functions and methods in Python. I'll look at what they are, how they work, and why you need them. 
By Ciprian Stratulat • Updated on Mar 2, 2023
blog image

Hello, and welcome back to a new article in my Intro to Programming series. Today, I'm going to take a look at functions and methods. 

 

 

What Are Functions and Methods in Python?

In this article, I'm going to build some intuition around functions and methods. Specifically, I'll look at what they are, how they work, and why you need them. You've already seen some functions. The print function is one example, and it's used for outputting strings to your screen. 

Image Source: Edlitera 

Type is another function, and this one takes an object, for example, a list, and tells you what its data type is. 

Image Source: Edlitera

You also saw a few examples of methods. Methods are also functions, except you can think of them as being attached to an object. So, for example, all list objects have an append method attached to them, and you can use that method to add another item to the list.

Image Source: Edlitera

Now, these two names, functions and methods, may be a bit intimidating, particularly if you associate them with math and you have bad memories of math. The idea behind them, however, is much simpler than the concepts you've encountered in math. 

You can think of a function as a box that takes some inputs – these can be strings, integer numbers, whatever you want – then performs some actions, typically using those inputs that you provided, and in the end, optionally, returns some result.

Image Source: Edlitera

The reason I say it optionally returns some results is because a function doesn't necessarily have to give you anything back:

Python function input with no output

Image Source: Screenshot of a Python function input with no output, Edlitera

For example, let's take the print function. You can picture it as this green box, right here. It has a name, print, and it accepts one input, which I drew as this blue dot. You can picture this input as perhaps a hole in the box. Through this hole, you can insert something in the box.

Let's say into this blue input hole, you insert a piece of paper that has the string hello there on it. This particular box doesn't have an output hole. Inside it, there's a mechanism that performs some actions, and then, in the end, you only observe the effects of that mechanism, which is that the string hello there magically appears on your computer screens.

I think this is a good analogy for functions. But some of these boxes, or let's call them functions from now on, do have an output hole as well:

Python function with an input and resulting output

Image Source: A screenshot of a Python function with an input and resulting output, Edlitera

Take len, for example. As you saw earlier, it's a built-in function that takes as an input some object and gives you a measure of the length of that object. So, if the object is a string, it will give you the number of characters in the string. If the object is a list, it will give you the number of items in the list. The analogy is the same. If you insert a piece of paper into the blue input hole with your list on it, some magic happens inside that box, and out of the red output hole, you see a piece of paper with the number 3 on it.

Now, perhaps you're wondering, why use functions? They're useful because you can use them to either achieve some effects (such as printing something on the screen) or get the answer to some questions (such as how long is the string hello). But more importantly, functions act a bit like shortcuts:

Python function shown as shortcut for code

Image Source: Screenshot of how a Python Function is a shortcut for code, Edlitera

If you take the len function I mentioned earlier, you can imagine that, inside that box, there are actually many lines of code that work hard to count whatever you may drop down the input hole. You can see in this diagram, code_line_1, code_line_2, etc. It doesn't matter right now what each line does. What matters is that there are a bunch of them. Maybe there are only 5 lines, maybe there are 50.

If you didn't have this box, this len function, whenever you wanted to count stuff, you would basically have to write out all those lines of code that actually achieve the counting. That's a problem for two reasons: first, you'll have to type a lot more and, by now, you know that more code means more typos and so possibly more problems. So you'll want to keep your code as short as possible. Secondly, without the concept of a function, you'd have to do a whole lot more work if you have to change something.

Think about it this way: what if you write a program where you need to count 10 different things at various points in the program? You'd have to repeat each one of those lines 10 times. And what if, once you're done, you realize you forgot something? Now you have a lot of places where you need to make changes.

So, functions allow you to create functionality that you can then easily use many times, with a single line of code. This is key. A very efficient programmer will look at their code, and whenever they see identical lines of code being repeated throughout the program, they see opportunities to create functions and simplify their code.

Image Source: Edlitera

Once that function is created, it can be used as many times as needed. So, in a sense, the more higher-level functions you write, the easier and faster it becomes to do things with code.

 

 

What is the Anatomy of a Function in Python?

I think by now, you've got some idea around the concept of a function. I compared it to a box, with separate holes for inputs and outputs. Now, I'll talk about how to build your own boxes. So far, I've only used built-in functions, so you're going to learn how to build your own custom functions. 

I'm first going to talk about the syntax for defining a function.

Here, I have a very simple function called say_hello. You define functions using the def keyword, which, of course, is short for define: 

 

Image Source: Edlitera

And the def keyword is followed by the name of the function.

Image Source: Edlitera

The same restrictions that apply to variable names also apply to function names. My advice to you is to get in the habit of giving your functions names that make sense, that is, names that kind of suggest what the function does.

For example, it would be terrible to name this function "function_one" or "penguin.Neither of those names tells you anything about what the function does, and reading code that has penguins in it makes no sense.

Image Source: Edlitera

After the name of the function, you have these two brackets. Inside them, you'll list any inputs that the function takes. In this case, the say_hello function has nothing inside the brackets, so it takes no inputs. The brackets are still required even if you have no inputs, so it's very important to put them there. I'll go over an example of a function that does take inputs in just a moment. 

Image Source: Edlitera 

Finally, on this line, just like you saw with if statements and for loops, you have a colon. It serves a similar role: it tells the Python interpreter that the body of the function is following next. This whole first line is the function header. 

Image Source: Edlitera 

This is where we specify the name of the function and the inputs it takes. What follows below is the internals of the function or the function body. This is where you write the code that the function will execute when it runs. 

Image Source: Edlitera

The important thing to remember about the function body is that it starts with the same 4 spaces indentation (or 1 tab, if you prefer) that you saw with if statements and for loops. The indentation is required by Python. Otherwise, it would have no way of telling which lines of code are inside the function and are, therefore, part of it, and which are outside of it.

 

 

How to Use Python Functions with Input

Now let's go over another example. This say_hello function is pretty good, but it doesn't do much. What if you wanted to have a custom greeting, depending on a customer's first name? 

You can do this by updating our function like this:

Image Source: Edlitera 

So, not much has changed here. The only thing is that now, between the brackets in the function header, you have this customer_name. This is the input name. The say_hello function now accepts an input, and the name of that input is customer_name.

You can name your inputs whatever you want. They are there just as placeholders to represent the values that will get passed to the function. However, as with variables, please choose names that make sense and that inform the reader of your code and what the inputs represent.

The body of the function is also pretty simple. It defines this variable called greeting, and you set it to the string that consists of the word hello with the customer_name appended at the end, and then you print the greeting.

 

How to Write Python Functions that Return a Value

So far, I've explored only functions that have some effect on the world but don't actually return a value to you. I mentioned earlier that an example of a function that takes some input and returns an output is len. Now, let's build your own function that takes some input and returns an output.

What might be an example of such a function?

A simple one would be a function that takes two numbers, let's say a and b, and returns their sum. Here's the code for it:

Image Source: Edlitera 

It looks pretty much like the other functions. It starts with the def keyword, followed by the name of the function, which is add. You will often find functions named as verbs or at least containing verbs. That's because you'll want to indicate in the function name what the function actually does.

So this function named add has two inputs represented by a and b here. You could also call them first_number and second_number, it's up to you. That's pretty much it for the function header – except, of course, don't forget about the colon at the end. As for the body, most of it should also hopefully be fairly straightforward. You define a variable called sum, and you set it to be a + b.

The last line is where the magic happens. Instead of printing, you write return sum. That return is a keyword, and it tells Python that what this function should do is expose the value of the variable sum as an output value.

Article continues below

 

How a Python Function Ends

A very important thing to remember about variables defined inside functions is that they are NOT available outside the functions where they are defined. A function is a bit of a black box – the outside world can only see what the function decides to expose via outputs, nothing more. So, for example, code running outside of this function would not be able to inquire about the value of the variable sum directly, unless we use the keyword return to expose that value.

 

To understand how that happens, consider this line of code. Here, we call (or run, or execute, whichever verb you prefer) the function add, and we pass it two inputs: 5 and 3. Function inputs are called parameters or arguments. There's a distinction between when you use the word parameters and the word arguments, but not everyone always makes it. We'll talk more about that in a second. Now, back to our add function – we call the add function, and we pass it the integer numbers 5 and 3 as inputs.

As you can see here, in order to capture the result, that is, to capture what this function returns, we simply assign the result of the function call to a variable. In this case, we store the result of running the add function in the variable named s.

I want to bring your attention to a few things here. First, when you run a function, you don't need the colon sign at the end because there's no function body following. Also, obviously, there's no def keyword involved either. That's only used for defining a function. Before you start writing some code, let's quickly clarify the difference between parameters and arguments.

Parameters are the variables used in the function definition. Arguments are the actual data that you pass to the function when you execute it:

Image Source: Edlitera 

As I mentioned, some people will use the two words interchangeably, even though technically that's not correct. It's not entirely crucial right now that you understand and memorize the distinction, but I just wanted you to be aware of it because you'll probably come across it.

So, when someone says parameters, they're referring to variables inside a function definition. When they talk about arguments, they are talking about the actual numbers, strings, lists, etc. that are passed to the function when they execute it.

 

Now, I'm going to get hands-on and start writing some code to explore functions in greater detail. 

 

How to Define a Python Function

I'll start by writing my initial say_hello function: def say_hello(): print('hello there'). Now I can run this function by typing say_hello(). Again, notice that even if I don't have any inputs, I still have to add the parentheses. What happens if I leave them out? Well, Python evaluates the name say_hello and concludes that it's a function with the name say_hello. But it does not actually run it.

To run a function, I need the parentheses:

# Let's define our function
def say_hello():
    print('hello there')

# and now we'll run it
say_hello()
# Our output will be hello there

# If we forget to add our parentheses
say_hello
# we get the output: <function __main__.say_hello>
# Don't forget the parentheses!

I'll also write a more advanced example, where I print a custom message: def say_hello(customer_name): greeting = 'hello ' + customer_name print(greeting). Remember, when you merge strings using the + operator, you have to add a space before the quotation mark if you want a space between the strings.

Notice that this function has the same name as the one above, so it basically overwrites it. If I  just run say_hello() like before, it will tell me I'm missing a value for customer_name. That makes sense since I updated the definition for say_hello to require the customer name as an input.

# Let's update our function
def say_hello(customer_name):
    greeting = 'hello ' + customer_name
    print(greeting)
# Notice the space after hello and before the '

# If we try to run it
say_hello()
# We get an error code:
# ---------------------------------------------------------------------------
#TypeError                                 Traceback (most recent call last)
#~\AppData\Local\Temp/ipykernel_27592/2814888808.py in <module>
#----> 1 say_hello()
#
#TypeError: say_hello() missing 1 required positional argument: 'customer_name'

This error message might be a bit cryptic at first, but the gist of it is that I'm trying to run the say_hello function, but it requires an input – some value to be assigned to the customer_name variable. So, I'll actually call it with an input; say_hello('Sam'), for example, will return hello Sam. say_hello('Lucy') will print hello Lucy, and so on.

 

 

# Let's add some input
say_hello('Sam')
# Our output will be hello Sam

# Let's add another input
say_hello('Lucy')
# Our output will be hello Lucy

You can also, of course, call this function in a loop.

For example, if I run for name in ['Sam', 'Lucy', 'Computer']: say_hello(name), I'll get a greeting for each name in my list, so hello Sam, hello Lucy, hello Computer.

# Let's create a loop by inputting a list
for name in ['Sam', 'Lucy', 'Computer]:
    say_hello(name)
# Our output will be hello Sam, hello Lucy, hello Computer

If you are following along in a Jupyter notebook, you can easily rerun a cell using Shift+Enter, so I'll use that to update that greeting to include proper capitalizations. I'll go into the cell where I defined my say_hello() function, change the greeting, and then rerun the code in the cell by pressing Shift+Enter. Then, when I come back to the cell that contains the for loop, I can click inside it, press Shift and Enter at the same time, and I'll see the updated output.

# Let's fix our function to use proper capitalization
# Change the cell to say
    greeting = 'Hello ' + customer_name
# Then press shift and enter to rerun the code
# When we rerun our list, we will get
# Hello Sam, Hello Lucy, Hello Computer

 

How to Run Python Functions with Default Values

I want to show you one more thing about function parameters. They can actually have a default value. So far, you've only seen instances where the parameter was required. The say_hello function requires that I pass an argument for the customer_name when I want to run the function. If the argument isn't passed, I'll get an error.

But there are situations where you might want to write functions that have parameters with default values. Let me show you an example. Let's generalize this say_hello function. Let's say that you're writing an automatic marketing platform and you need a way to greet your customers who are in different parts of the world.

You can't just use the word "hello" – that will only work with customers who speak English. But let's also say that most, though not all, speak English. In that case, it would make sense to set "hello" to be the default greeting, but you would also want to have a way to specify a different greeting in some cases.

You can achieve that using a default parameter. Let me show you.

Let's rewrite the say_hello function to be def say_hello(customer_name, greeting_word='Hello '): greeting = greeting_word + customer_name print(greeting). What did I do here? Well, the function looks pretty similar to what I had before, except I now have a second parameter, named greeting_word, and I assigned to that parameter the default value Hello.

This is the default parameter. So now, the greeting is made up of the greeting_word and the customer_name.

# Let's rewrite our function with an added parameter
def say_hello(customer_name, greeting_word='Hello '):
    greeting = greeting_word + customer_name
    print(greeting)

Let's call this function. If I call it like before, say_hello('Sam'), the output is Hello Sam. Notice that I didn't specify a greeting_word, only a customer_name, so my greeting word was set to the default Hello. What if Sam is French?

I can then instead execute say_hello('Sam', 'Bonjour '), and we see that the greeting is now bonjour Sam. I added an extra space after bonjour so that there would be a space between bonjour and Sam. Remember that when we merge strings using the plus operator, a space is not added by default, so you have to add one yourself if you want one.

# Let's call our function
say_hello('Sam')
# Our output will be Hello Sam

# Let's adjust our greeting word and run it again
say_hello('Sam', 'Bonjour ')
# Our output is now Bonjour Sam


Default parameters are good to know and use. Lots of built-in Python functions have default parameters, and you can discover them using Python documentation.

Next, let's go over a few examples of functions that return results. A very basic one that we saw earlier is a function that adds two numbers. Let's write that: def add(a, b): sum = a + b return sum. Now, we can call that function, let's say result = add(5,3). Notice that nothing got printed on the screen this time, but if we print(result), we get 8.

What happened here is that we executed the add function with two arguments, the integer 5 and the integer 3, and the result of that execution got stored in the variable named result. We then printed the value stored in result, and we got 8. Hopefully, that makes sense.

# Let's define our function
def add(a, b):
    sum = a + b
    return sum

# If we call our function
result = add(5,3)
# nothing is printed in the console

# Let's try the print function
print(result)
# Our output is now 8

What happens if I accidentally run result = add(0)? Well, we get an error – and we've seen this error before. Basically, Python saw the integer number 0 and assigned that in the place of a but saw nothing for b because we didn't pass a second argument. Try calling the add function again. Let's say res = add(0, 5). Now, when we print(res), we get 5.

# Let's see what happens when we run
result = add(0)
# We get the following error:
#---------------------------------------------------------------------------
#TypeError                                 Traceback (most recent call last)
#~\AppData\Local\Temp/ipykernel_27592/2149202480.py in <module>
#----> 1 result = add(0)
#
#TypeError: add() missing 1 required positional argument: 'b'

# Let's fix our code by adding a second integer
result = add(0,5)
print(res)
# Our output is now 5

Perhaps you're wondering – why not just print the result directly instead of assigning it to this variable? Well, we could do that, of course, and in this case, it would work the same. However, oftentimes, we define functions that compute some sort of intermediary value that we need to reuse throughout our program.

We need a way to store that value so we can reuse it later. In that case, printing won't help you. Printing a value does not store it in memory anywhere – it's printed, and then it's gone. If you want to store the result of some function execution, you need to define the function in such a way that it returns a result using the keyword return, and when you execute that function, you need to assign the result to some variable.

 

How to Use Return in a Python Function

One more thing: you can only use return once inside a function and only as the last line of the function's code. Once the keyword return is reached, the function terminates, and the result is returned. Let me show you.

I'll modify my add function and I'll add a print function after the return. So now, I have def add(a, b): sum = a + b return sum print('hey'). And now, let's write res = add(2,2). Notice something? The word hey was not printed.

I know that the function executed successfully because if I print(res) now, I get 4, which is indeed the result of 2 + 2. But the word hey was not printed. Why is that? It's because once the code execution reaches the keyword return, the function terminates.

# Let's update our add function
def add(a, b):
    sum = a + b
    return sum
    print('hey')
# and run it
res = add(2,2)
# Nothing is printed in the console

# Let's check our result
print(res)
# Our output is now 4

Compare that with this: def add(a, b): sum = a + b print('hey') return sum. Here, I'm printing hey before I return the result. So now, let's write res = add(2,2), and if I run this, I can see that the word hey was printed. Not only that but, of course, if I print(res) now, I see that res was also updated with the correct result value.

So, to sum up, once the execution of a function reaches the return keyword, the function terminates.

# Let's update our code again
def add(a, b):
    sum = a + b
    print('hey')
    return sum
# Now when we run the code
res = add(2,2)
# Our output is 'hey'

print(res)
# Our output is 4

 

How to Write Python Functions with Multiple Exit Points

So far, I've only built functions with a single exit point. Some code gets executed, and a single possible output gets returned or printed at the end. Next up, I'll look at functions that have more than one exit point.

There's one more thing you need to be aware of when it comes to using the return keyword in Python. While you can only execute a single return statement in a function, you can still have multiple exit paths possible inside that function. Let me explain.

For example, let's consider two integers representing the dollar amount of two recent purchases that a customer made. You want to write a function that takes the two numbers as inputs and checks whether the customer has a high balance. You can define a high balance as amounts greater than 1000 dollars.

You can write the following solution: first, you define the function. Let's call it has_high_balance, so def has_high_balance(a, b):. a and b here represent the dollar amounts of the two purchases that the customer made most recently. Next, you calculate their sum, so sum = a + b. Now, you need to check if that sum is greater than 1000, so if sum > 1000: return True. What this means is that your function will return the boolean value True if the sum of the two purchases is higher than 1000 dollars. In other words, your function will return True if the customer has a high balance.

Next, you'll write the else branch, so else: return False. So now, if the sum is not greater than 1000, it will return False.

# Let's define our function
def has_high_balance(a, b):
    sum = a + b
    if sum > 1000:
        return True
    else: 
        return False

Let's go ahead and run this and check that it works. You can define a variable named is_high_balance, and you'll first set it to the result of calling the has_high_balance function with 400 and 500 as inputs. So is_high_balance = has_high_balance(400, 500).

If you now print(is_high_balance), you'll get False, and that makes sense because 400 + 500 is 900, which is less than 1000.

Let's do this again. This time is_high_balance = has_high_balance(1000, 200). If you print(is_high_balance) now, you get True, because 1000 + 200 is 1200, which is greater than 1000, so the customer is running a high balance.

# Let's run our function with 400 and 500
is_high_balance = has_high_balance(400, 500)
# and print it
print(is_high_balance)
# Our output is False

# Let's try 1000 and 200
is_high_balance = has_high_balance(1000, 200)
# and print
print(is_high_balance)
# Our output is True

This is not the shortest or the prettiest implementation of a function, but I wrote the solution this way to show you that you can have multiple return keywords in a function if they correspond to different exit points out of the function.

In this case, if the sum is greater than 1000, you return some value, in this case, the boolean value True, and if the sum is not greater than 1000, you return a different value, in this case, the boolean value False. If a function has an if statement inside it, it is pretty common to have multiple return statements, one per branch typically.

Let’s wrap up this exploration of functions by clarifying a couple of important points.

 

How to Write Nested Functions in Python

The first point is that a function can actually call other functions inside it. There's nothing preventing you from doing that. In fact, it's very common. The second one has to do with how function execution affects the order in which particular lines of code are executed.

Let's revisit the high_balance function. See the line where I calculate the sum? I could replace that with a call to my add function that I wrote above. So, the code now becomes: def has_high_balance(a, b): sum = add(a,b) if sum > 1000: return True else: return False. It looks very similar to what I had earlier, except instead of using the + operator to add the numbers a and b, I call the add function that I defined previously. This is totally valid code.

# Let's update our function
def has_high_balance(a, b):
    sum = add(a,b)
    if sum > 1000:
        return True
    else: 
        return False

I'll run it again to check it. So again, I run is_high_balance = high_balance(1000, 200). You see that the word hey got printed. If you look above at my last definition for the add function, you can see that I'm printing the word hey before I return the value. I probably don't need to do that, but that's what I had my add function do, so that's ok.

Now, if I try print(is_high_balance), I again get True, which makes sense because 1000 + 200 is 1200 which is greater than 1000. So, functions can actually call other functions inside their definition, and this is very powerful because it allows for code reuse.

# Let's run our function with 1000 and 200 again
is_high_balance = has_high_balance(1000, 200)
# Our output is hey because of our add function

# Let's print the result
print(is_high_balance)
# Our output is True

 

What is the Execution Order of Python Functions?

Finally, there's one last point that I want to insist on. That is that the function execution affects the order in which particular lines of code are executed. Let me show you what I mean by that.

I'm going to write a slightly longer piece of code, and I'll go over it in just a second. See if you can figure out what this program does before I go over it.

Before I go over this little program, let’s toggle the line numbers for this cell. I can easily do this by clicking the Keyboard icon in the menu bar of our Jupyter notebook, searching for the word "line," and clicking on "toggle line numbers." The Keyboard icon shows me all the commands that I can run in Jupyter notebook and their corresponding keyboard shortcuts.

If you use Jupyter notebook a lot, I encourage you to memorize some of the shortcuts that you use all the time because it's going to make you a faster programmer:

Screenshot of Python Function execution order in Jupyter Notebook

Image Source: Screenshot of Python Function execution order in Jupyter Notebook, Edlitera

 

You can see some nice line numbers on the side here. So, what does this program do? Let's go line-by-line. On line 1, I define a variable x, and I assign it the integer value 5. On line 2, I define a variable y, and I assign it the integer value 7. On lines 4 and 5, I define a function called sum that takes two integer numbers and returns their sum. On line 7, I define a variable called sum1, and I assign it the result of executing the sum function with the input values stored in variables x and y.

 

Let's focus a bit on line 7. When my program execution reaches this line, what happens next is that the Python interpreter figures out that the sum function is defined on line 4 above, so it jumps to that line, sets a to be whatever is stored in my variable x, which is 5, and then sets b to whatever is stored in my y variable, which is 7, then goes to line 5, calculates a + b, so 5 + 7, which is 12, and returns 12. Then, it jumps back to line 7 and assigns 12 to the variable named sum1. Following that, normal execution resumes, so the next code line that it runs is line 8, which prints sum1, so it prints 12.

Next, it executes line 10, where it updates x and sets it to the integer 10. Next, the program execution runs line 11, where it updates y and sets it to the integer 10. Next, on line 12, it sees that you are again running the sum function. It again figures out that the function is defined on line 4 above, so it jumps to that line, sets a to be whatever value I have in x, which now is 10, and then sets b to whatever value I have in y, which is also 10, then goes to line 5, calculates a + b, so 10 + 10, which is 20, and then returns 20. Then, it jumps back to where it was before, to line 12, and assigns the value 20 to the variable named sum2. Following that, once again, normal execution resumes, so the next code line that runs is line 13, where the program prints the value stored in sum2, which is 20.

If you run this code, you'll see that the outputs are indeed 12 and 20. So, when you define a function, the function is not actually run. A function definition is the act of creating the function, not of using that function. A function definition is just a way to tell the Python interpreter “hey, I made this box that takes these inputs, does this magic thing, and returns this result, and I want you to be aware that this box exists because I'm going to use it at some point in the future”. So on lines 4 and 5, you just define your function, and you tell the Python interpreter about it.

Following that, code lines are executed in normal order, top to bottom, one after the other, until I hit a function execution. On line 7, the Python interpreter sees that you are trying to run the function that you created earlier, so it basically jumps back to the function body. Then, after it assigns to each parameter the values that you passed to the function, it runs the code lines inside the function body one-by-one, in order.

Once the function finishes, it returns to where it left off, which, in this case, is line 7, and then again continues line-by-line. So, the important thing to remember here is that after a function is run, the program execution returns to the specific line that called the function.

That's all for now as far as functions are concerned. Functions are incredibly powerful building blocks, and you'll use them heavily, so spend some time practicing writing them and using them. In addition to the practice exercises we provide, you can also design your own by challenging yourself to write your own functions and then run them with different parameters.

 

Ciprian Stratulat

CTO | Software Engineer

Ciprian Stratulat

Ciprian is a software engineer and the CTO of Edlitera. As an instructor, Ciprian is a big believer in first building an intuition about a new topic, and then mastering it through guided deliberate practice.

Before Edlitera, Ciprian worked as a Software Engineer in finance, biotech, genomics and e-book publishing. Ciprian holds a degree in Computer Science from Harvard University.