We closed the previous lesson with easytask.py working and an obvious limitation: all it does is print fixed text, and changing a piece of data means editing the code by hand. The temptation now is to rush off and learn syntax to fix it. We are going to resist that for one more lesson, because the skill that really separates whoever programs from whoever copies code is still missing: thinking the solution through before writing it.

A programmer in a hurry opens the editor and starts typing. Two hours later they have code that almost works, they do not know why it fails and they cannot explain to anyone what it does. A skilled programmer spends the first twenty minutes on paper: they understand the problem, break it down, write out the steps and check them by hand. Then they type, and they type little, because they already know what they are going to write.

In this lesson you will learn what an algorithm is exactly, how to break a problem down, how to express a solution in pseudocode and in a flowchart, and how to verify it without a computer by means of a desk check. All applied to two real EasyTask operations.

Constructions such as "repeat" or "if...then" will appear: here we will use them purely as a way of thinking. Writing them in Python is a matter for modules 2 and 3.

Contents

  1. What an algorithm is
  2. The five properties of an algorithm
  3. Breaking a problem into steps and subproblems
  4. Pseudocode: conventions and example
  5. Flowcharts
  6. Desk check: verifying without a computer
  7. From pseudocode to Python code
  8. EasyTask: registering a new task
  9. EasyTask: which task Marta tackles first
  10. Common mistakes and tips
  11. Exercises
  12. Conclusion

  1. What an algorithm is

An algorithm is a finite sequence of precise steps which, starting from some input data, produces a result in a finite time.

The word sounds technical, but the concept is an everyday one: a cooking recipe, the assembly instructions for a bookshelf and the multiplication procedure you learned at school are all algorithms. They share the essentials: ordered, unambiguous steps that terminate and produce something.

The important part is this: an algorithm is independent of the programming language. The same algorithm for sorting a list can be written in Python, in Java or on paper; what changes is the notation, not the idea. That is why what you learn here will serve you even if you change language ten times.

Concept What it is Analogy
Problem What has to be solved "I want a cake"
Algorithm The method for solving it The recipe
Program The algorithm written in an executable language The recipe translated into orders for a kitchen robot
Execution The program running with specific data Making the cake on Saturday

Confusing these four things is the source of a lot of frustration. "My program isn't working" usually really means "I am not clear about the algorithm", and that is not fixed by fiddling with code.

  1. The five properties of an algorithm

Not every list of steps is an algorithm. It must satisfy five properties.

1. Precise. Each step states exactly what to do, with no room for interpretation.

  • Bad: "review the important tasks".
  • Good: "for each task, if its priority is high and its status is pending, show it on screen".

2. Definite (deterministic). With the same inputs it always produces the same outputs. If you run the algorithm twice with the same data, the result is identical.

3. Finite. It terminates after a finite number of steps. An algorithm that does not terminate is not an algorithm, it is an infinite loop, and it is one of the most common errors when starting out.

  • Bad: "repeat: count one more task" (it never ends).
  • Good: "repeat while there are tasks left to review" (it ends, because tasks are finite and each round leaves one fewer).

4. With defined input. Zero or more starting data items, clearly specified. In "register a task", the input is the title, the description, the assignee and the priority.

5. With defined output. At least one observable result: a value, a message on screen, a modified file. An algorithm that produces nothing observable is of no use at all.

Property Control question If it fails...
Precise Can anyone read this step in two ways? The program will do something unexpected
Definite Will it always give the same result with the same data? Errors impossible to reproduce
Finite Is it guaranteed to terminate? The program hangs
Defined input What data do I need and where does it come from? Data will be missing halfway through
Defined output What does it produce and how do I see it? Nobody will know whether it worked

Apply these five questions to any algorithm you write. They catch most faults before they exist.

  1. Breaking a problem into steps and subproblems

The central technique for tackling any problem is called decomposition: splitting a large problem into smaller problems until each piece is obvious. Each piece is solved separately and then they are combined.

Applied to EasyTask, Marta's initial request was: "I want a program to manage the studio's tasks". Like that, as one block, it is unmanageable. Decomposed:

flowchart TD
    A["Manage Alba Studio tasks"] --> B["Register a task"]
    A --> C["Query tasks"]
    A --> D["Update tasks"]
    A --> E["Save the information"]
    B --> B1["Ask for the data"]
    B --> B2["Validate priority and assignee"]
    B --> B3["Add to the list"]
    C --> C1["List them all"]
    C --> C2["Filter by assignee"]
    C --> C3["Filter by status"]
    D --> D1["Mark as completed"]
    D --> D2["Change assignee"]

Each leaf of the tree is a small task we do know how to tackle. "Mark as completed" is a manageable problem; "manage the studio's tasks" was not.

Decomposition brings three concrete advantages:

  • Visible progress. Solving one leaf of the tree takes minutes, and you see movement. Facing the whole problem produces paralysis.
  • Localised errors. If filtering by assignee fails, you know exactly which piece to look at.
  • Reuse. "Validate priority" serves both when registering a task and when modifying one. Writing it once and using it twice is the basis of module 4.

A practical criterion for knowing when to stop decomposing: when you can explain the piece in one sentence and you know how to do it by hand. If you are still unsure, keep splitting.

  1. Pseudocode: conventions and example

Pseudocode is a way of writing algorithms halfway between English and code: structured enough not to be ambiguous, free enough not to fight with syntax.

There is no official pseudocode. These are the conventions we will use in the course:

Element Notation Example
Start and end START / END
Data input READ <item> READ title
Data output DISPLAY <message> DISPLAY "Task saved"
Assign a value <name> ← <value> counter ← 0
Condition IF <cond> THEN ... ELSE ... END IF
Conditional repetition WHILE <cond> DO ... END WHILE
Repetition over elements FOR EACH <x> IN <list> DO ... END FOR
Comment // text // ignored when running

Two important style rules: indent the contents of each block (it makes it easy to see where it starts and ends) and always close the blocks (END IF, END WHILE).

A complete example. An algorithm that works out how many pending tasks the studio has:

START
    // Input: a list of tasks, each with its status
    // Output: the number of pending tasks

    pending ← 0

    FOR EACH task IN task_list DO
        IF status of task = "pending" THEN
            pending ← pending + 1
        END IF
    END FOR

    DISPLAY "Pending tasks: " + pending
END

Let us run the five properties over this algorithm:

  • Precise: each step is unambiguous.
  • Definite: the same list always produces the same number.
  • Finite: the list has a finite number of elements and each round consumes one.
  • Defined input: the list of tasks with their statuses.
  • Defined output: a message with the number of pending tasks.

It satisfies all five. It is a correct algorithm, and we have verified it without switching the computer on.

  1. Flowcharts

A flowchart represents the algorithm graphically. It is especially useful when there are decisions and alternative paths, because they can be seen at a glance.

The basic symbols:

Symbol Shape Meaning
Start / End Oval Where it starts and ends
Process Rectangle An action or calculation
Decision Diamond A question with Yes/No exits
Input / Output Parallelogram Reading data or showing results
Arrow The order in which you move on

Example 1: is the task urgent? An algorithm with a single decision.

flowchart TD
    A(["START"]) --> B[/"READ priority of the task"/]
    B --> C{"priority = high?"}
    C -->|Yes| D[/"DISPLAY: Deal with it today"/]
    C -->|No| E[/"DISPLAY: It can wait"/]
    D --> F(["END"])
    E --> F

Note two things: exactly two labelled arrows leave the diamond, and both paths join up again before the end. A diagram with paths that do not finish at END is incomplete.

Example 2: counting the pending tasks. The same algorithm as in the previous section, now with repetition.

flowchart TD
    A(["START"]) --> B["pending <- 0"]
    B --> C["Move to the first task"]
    C --> D{"Any tasks left<br/>to review?"}
    D -->|No| H[/"DISPLAY pending"/]
    D -->|Yes| E{"Is its status<br/>pending?"}
    E -->|Yes| F["pending <- pending + 1"]
    E -->|No| G["Move to the next task"]
    F --> G
    G --> D
    H --> I(["END"])

The loop shows up as what it is: an arrow that goes back, from G to D. And there lies the key to finiteness: G moves on to the next task on every round, so at some point the answer to D will be "No". If G did not exist, the diagram would spin forever. That return arrow is the infinite loop made visible, and it is a good reason to draw before you program.

Tool When it is worth using
Pseudocode Long algorithms, many sequential steps, already close to the code
Flowchart Few steps but many decisions; explaining the logic to someone else

In practice both are used: the diagram to grasp the overall shape, the pseudocode for the detail.

  1. Desk check: verifying without a computer

A desk check (or trace) consists of running the algorithm by hand, with specific data, noting in a table how each value changes step by step. It is the most underrated and most profitable technique in the whole trade: it catches logic errors in five minutes that would cost an hour at the computer.

Let us trace the pending-count algorithm with this Alba Studio data:

No. Task Assignee Status
1 Solé Bakery logo Luis pending
2 Book fair poster Nuria completed
3 Vidal account website redesign Luis pending
4 March quote Marta pending

And now the trace table. One row per loop round, one column per value we care about:

Round Task examined Status Is it pending? pending afterwards
(start) 0
1 Solé Bakery logo pending Yes 1
2 Book fair poster completed No 1
3 Vidal account website redesign pending Yes 2
4 March quote pending Yes 3
(end: no tasks left) 3

Output: Pending tasks: 3. Counting by eye in the data table: numbers 1, 3 and 4. Correct.

How to do a desk check properly:

  1. Choose small but representative data. Four elements are enough; with forty you will get tired and skip steps.
  2. One column for each value that changes. If there are three variables, three columns.
  3. One row per step or round. No grouping and no "speeding up": the error is usually precisely where something is taken for granted.
  4. Be the machine, not the author. Do not do what you meant to write, do what is written. This is where the errors show up.
  5. Test the edge cases too. What if the list is empty? With our algorithm the loop never runs and the output is 0. Correct. What if they are all completed? Also 0. Correct.

That last point deserves emphasis: edge cases are where most real errors live. Empty list, a single element, all the same, repeated values. Always check them.

  1. From pseudocode to Python code

Once the algorithm is clear and traced, translating it into Python is almost mechanical. Let us see it with an example we can already write in full: showing a task card.

Pseudocode:

START
    DISPLAY "--- TASK CARD ---"
    DISPLAY "Title: Sole Bakery logo"
    DISPLAY "Assignee: Luis"
    DISPLAY "Priority: high"
    DISPLAY "Status: pending"
END

Python:

print("--- TASK CARD ---")
print("Title: Sole Bakery logo")
print("Assignee: Luis")
print("Priority: high")
print("Status: pending")

The correspondence is direct: DISPLAY becomes print, and the text goes between quotes inside the parentheses. START and END are not translated: in Python the program begins at the first line and ends at the last.

This is the complete translation table, which we will fill in as the course goes on:

Pseudocode Python Where it is studied
DISPLAY x print(x) You already know it
READ x x = input() Input and output
x ← 5 x = 5 Variables
IF ... THEN if ...: Conditionals
ELSE else: Conditionals
WHILE ... DO while ...: Loops
FOR EACH x IN list for x in list: Loops

For now you only have the first row. By the end of module 3 you will have them all, and you will be able to translate into Python any algorithm you write in pseudocode. In the meantime, write pseudocode without fear: the algorithm is the valuable part, and translating it will be the easy bit.

  1. EasyTask: registering a new task

Let us apply all of the above to EasyTask's first requirement (R1 and R2 from the list in the first lesson): registering a task with its five data items, validating the priority.

The problem. Marta wants to note down a new job. We have to ask her for the title, description, assignee and priority; the status always starts as "pending". The priority can only be high, medium or low: if she types anything else, we have to ask again.

Pseudocode:

START
    // Input: data typed by the user
    // Output: the registered task and a confirmation message

    DISPLAY "--- NEW TASK ---"

    READ title
    READ description
    READ assignee

    // Validation: repeat until the priority is valid
    priority_valid ← FALSE
    WHILE priority_valid = FALSE DO
        DISPLAY "Priority (high / medium / low):"
        READ priority
        IF priority = "high" OR priority = "medium" OR priority = "low" THEN
            priority_valid ← TRUE
        ELSE
            DISPLAY "Invalid priority. Please try again."
        END IF
    END WHILE

    status ← "pending"

    SAVE the task (title, description, assignee, priority, status)
    DISPLAY "Task registered successfully."
END

Flowchart:

flowchart TD
    A(["START"]) --> B[/"READ title, description,<br/>assignee"/]
    B --> C[/"READ priority"/]
    C --> D{"is priority<br/>high, medium or low?"}
    D -->|No| E[/"DISPLAY: invalid priority"/]
    E --> C
    D -->|Yes| F["status <- pending"]
    F --> G["SAVE the task"]
    G --> H[/"DISPLAY: task registered"/]
    H --> I(["END"])

Note the arrow that goes back from E to C: that is the validation loop. As long as the priority is wrong, the question is asked again. And it is finite provided the user eventually types something valid; if they type nonsense indefinitely, the program will ask indefinitely. That is not an error in the algorithm: it is a design decision worth taking consciously. An alternative would be to allow three attempts and then cancel.

Desk check. Marta registers a job and gets the priority wrong:

Step Action Value entered State of the algorithm
1 READ title "Vidal business cards" title assigned
2 READ description "300 units, double-sided" description assigned
3 READ assignee "Nuria" assignee assigned
4 READ priority "urgent" not valid → error message
5 READ priority "HIGH" is it valid?
6 READ priority "high" valid → leaves the loop
7 status ← pending status = "pending"
8 SAVE and DISPLAY "Task registered successfully."

Step 5 reveals a problem that the desk check has brought to the surface: "HIGH" in capitals does not match "high", because they are different pieces of text. The algorithm, as written, would reject it. Is that what we want? Almost certainly not: Marta will type in capitals half the time.

The fix is simple — convert what is entered to lower case before comparing — and we will apply it in Type conversion and validation. What matters here is how we found the fault: with a table and a pencil, before writing a single line of Python. Finding it later, with the program already written, would have cost far more.

  1. EasyTask: which task Marta tackles first

A second algorithm, this time about deciding. Marta arrives on Monday, has several tasks assigned and wants to know which one to start with.

The business rule, agreed with the team:

  1. Only pending tasks count: completed ones are ignored.
  2. Among the pending ones, the one with the highest priority wins (high > medium > low).
  3. If there is a tie on priority, the one registered earlier wins.

Note that these three rules are not invented by the programmer: they are agreed with whoever has the problem. Writing them down explicitly is part of the job, and often the first time the team has agreed on something they thought was obvious.

Pseudocode:

START
    // Input: Marta's list of tasks, in registration order
    // Output: the task she should tackle first

    chosen ← NONE

    FOR EACH task IN martas_tasks DO

        IF status of task = "completed" THEN
            // of no interest to us, move on to the next one
        ELSE
            IF chosen = NONE THEN
                chosen ← task
            ELSE
                IF priority of task is higher than priority of chosen THEN
                    chosen ← task
                END IF
                // if it is equal or lower, the previous one stays:
                // this way the one registered earlier wins on a tie
            END IF
        END IF

    END FOR

    IF chosen = NONE THEN
        DISPLAY "You have no pending tasks."
    ELSE
        DISPLAY "Start with: " + title of chosen
    END IF
END

Flowchart:

flowchart TD
    A(["START"]) --> B["chosen <- NONE"]
    B --> C{"Any tasks left<br/>to review?"}
    C -->|No| J{"chosen = NONE?"}
    C -->|Yes| D{"Is it completed?"}
    D -->|Yes| I["Move to the next one"]
    D -->|No| E{"chosen = NONE?"}
    E -->|Yes| F["chosen <- current task"]
    E -->|No| G{"Is its priority higher<br/>than that of chosen?"}
    G -->|Yes| F
    G -->|No| I
    F --> I
    I --> C
    J -->|Yes| K[/"DISPLAY: no pending tasks"/]
    J -->|No| L[/"DISPLAY: start with chosen"/]
    K --> M(["END"])
    L --> M

Desk check. Marta's tasks, in registration order:

No. Title Priority Status
1 Vidal account quote medium pending
2 Review February invoices low completed
3 Call Solé Bakery high pending
4 Prepare team meeting high pending

Trace round by round:

Round Task Completed? chosen before Replaces? chosen after
1 Vidal quote (medium) No NONE Yes, there was none Vidal quote
2 Review invoices (low) Yes Vidal quote No, it is ignored Vidal quote
3 Call Solé (high) No Vidal quote Yes: high > medium Call Solé
4 Prepare meeting (high) No Call Solé No: high is not higher than high Call Solé

Output: Start with: Call Solé Bakery.

Let us verify that this is correct according to the agreed rules. There are three pending tasks: Quote (medium), Call Solé (high) and Prepare meeting (high). Two are high priority, and between them the one registered earlier wins, which is "Call Solé" (number 3 against number 4). It matches what the algorithm produces.

And the edge cases:

  • Marta has no tasks at all. The loop never runs, chosen stays NONE and "You have no pending tasks" is shown. Correct.
  • They are all completed. They all fall down the branch that ignores them, chosen stays NONE and the same message comes out. Correct.
  • Only one pending. It is assigned on its round and nothing replaces it. Correct.

The algorithm holds up in all four scenarios. It is ready to be implemented, and we will do so once we have conditionals and loops in Python.

Common Mistakes and Tips

Starting to type without having thought the algorithm through. It is the costliest mistake. If you cannot say out loud what steps your program is going to take, you are not ready to write it. Paper first.

Ambiguous steps. "Check whether the task is important" is not a step: it does not say what important means. "Check whether its priority is high" is. Every ambiguity you leave in the algorithm will reappear as an error in the program.

Forgetting to advance inside a loop. If nothing changes on each round to bring the end closer, the loop is infinite. It is easy to spot in the diagram: look for the box that makes the diamond's condition progress. If you cannot find it, you have a problem.

Skipping the edge cases. Empty list, a single element, all with the same value, all completed. An algorithm that only works with the pretty case does not work.

Doing the desk check "as the author" and not "as the machine". When tracing you tend to run what you meant to write, not what you wrote. It is the subtlest trap. A trick: trace an algorithm of your own from a week ago, or swap with someone else.

Taking business rules for granted. "The highest priority wins" seems complete until two tasks tie. Always write down what happens on ties, on empty data and on incorrect data. Asking beforehand is cheap; discovering it in production is not.

Believing that pseudocode has to be perfect. It is not a deliverable, it is a thinking tool. Cross it out, rewrite it, use arrows in the margin. If it helps you think, it is well written.

A final tip: when a program comes out wrong, do not start changing code to see if you hit on the answer. Go back to the algorithm and give it a desk check. In most cases the error is not in the syntax, it is in the reasoning.

Exercises

Exercise 1: Spotting unmet properties

For each algorithm, state which property or properties it fails to meet and correct it:

a)

START
    WHILE there are tasks DO
        DISPLAY "There are pending tasks"
    END WHILE
END

b)

START
    READ priority
    IF the priority seems urgent THEN
        DISPLAY "Deal with it soon"
    END IF
END

c)

START
    counter ← 0
    FOR EACH task IN list DO
        counter ← counter + 1
    END FOR
END

Exercise 2: Algorithm and diagram for counting one person's tasks

Write the pseudocode and the flowchart of an algorithm that, given the studio's list of tasks and a person's name, shows how many pending tasks they have assigned. Then do the desk check with this data, looking for Luis's:

No. Title Assignee Status
1 Solé Bakery logo Luis pending
2 Book fair poster Nuria pending
3 Vidal website redesign Luis completed
4 Solé brand manual Luis pending
5 March quote Marta pending

Exercise 3: Reassigning a task

Marta wants to be able to reassign a task to another member of the team. The agreed rules are:

  • Only pending tasks can be reassigned; a completed one is not touched.
  • The new assignee must be Marta, Luis or Nuria.
  • There is no sense in reassigning to the same person who already has it: a warning is needed.

Write the pseudocode of this algorithm and do a desk check for these three cases: (a) reassigning to Nuria a pending task of Luis's, (b) trying to reassign a completed task, (c) reassigning to Luis a task that is already Luis's.

Solutions

Solution 1.

a) It fails finiteness. Nothing inside the loop reduces the number of tasks or advances through the list, so the condition never stops holding: the message would be printed forever. It also has no useful output (it repeats the same thing without reporting anything). Correction:

START
    pending ← 0
    FOR EACH task IN list DO
        IF status of task = "pending" THEN
            pending ← pending + 1
        END IF
    END FOR
    DISPLAY "Pending tasks: " + pending
END

b) It fails precision (and therefore determinism). "Seems urgent" cannot be evaluated: two people would read it differently and the machine cannot read it at all. Correction:

START
    READ priority
    IF priority = "high" THEN
        DISPLAY "Deal with it soon"
    ELSE
        DISPLAY "It can wait"
    END IF
END

The ELSE has been added so that there is output in both cases.

c) It fails defined output. It works the counter out correctly but never shows it: the result is lost when it finishes. Correction: add DISPLAY "Total tasks: " + counter before END.

Solution 2.

Pseudocode:

START
    // Input: list of tasks and a person's name
    // Output: number of pending tasks for that person

    READ person
    counter ← 0

    FOR EACH task IN task_list DO
        IF assignee of task = person AND status of task = "pending" THEN
            counter ← counter + 1
        END IF
    END FOR

    DISPLAY person + " has " + counter + " pending tasks."
END

Flowchart:

flowchart TD
    A(["START"]) --> B[/"READ person"/]
    B --> C["counter <- 0"]
    C --> D{"Any tasks left<br/>to review?"}
    D -->|No| H[/"DISPLAY person and counter"/]
    D -->|Yes| E{"Is it that person's<br/>AND is it pending?"}
    E -->|Yes| F["counter <- counter + 1"]
    E -->|No| G["Move to the next one"]
    F --> G
    G --> D
    H --> I(["END"])

Desk check looking for Luis's:

Round Task Is it Luis's? Pending? Does it count? counter
(start) 0
1 Solé Bakery logo Yes Yes Yes 1
2 Book fair poster No Yes No 1
3 Vidal website redesign Yes No No 1
4 Solé brand manual Yes Yes Yes 2
5 March quote No Yes No 2

Output: Luis has 2 pending tasks. Verified by eye: numbers 1 and 4. Number 3 is Luis's but it is completed, and that is precisely the case that checks the double condition works.

Solution 3.

START
    // Input: a task and the name of the new assignee
    // Output: the reassigned task, or a message explaining why not

    READ task
    READ new_assignee

    IF status of task = "completed" THEN
        DISPLAY "Cannot reassign: the task is already completed."
    ELSE
        IF new_assignee IS NOT "Marta" NOR "Luis" NOR "Nuria" THEN
            DISPLAY "Invalid assignee. It must be Marta, Luis or Nuria."
        ELSE
            IF new_assignee = assignee of task THEN
                DISPLAY "The task is already assigned to " + new_assignee
            ELSE
                assignee of task ← new_assignee
                DISPLAY "Task reassigned to " + new_assignee
            END IF
        END IF
    END IF
END

Desk check:

Case Task (assignee, status) New assignee Checks Result
a Solé logo (Luis, pending) Nuria Not completed → valid assignee → different from the current one assignee ← Nuria. "Task reassigned to Nuria"
b Vidal website redesign (Luis, completed) Nuria Completed → it stops at the first condition "Cannot reassign: the task is already completed."
c Brand manual (Luis, pending) Luis Not completed → valid assignee → same as the current one No changes. "The task is already assigned to Luis"

Notice the order of the checks: first the status, then the validity of the name and lastly the match. That order matters. If we checked the assignee match first, a completed task assigned to Luis that someone tried to reassign to Luis would give the wrong message. Deciding the order in which the checks are made is part of designing the algorithm, not a detail.

Conclusion

Programming starts a long way from the keyboard. In this lesson you have learned that an algorithm is a finite sequence of precise steps that turn an input into an output, independent of the language it is written in, and that it must satisfy five properties: being precise, definite, finite, with defined input and with defined output. You have seen how to decompose a large problem into manageable subproblems, how to express the solution in pseudocode with clear conventions and how to draw it in a flowchart where loops show up as arrows that go back. And you have practised the most profitable technique in the trade: the desk check, which with a table and a pencil finds errors that would cost hours at the computer — such as the "HIGH" that did not match "high".

All of it applied to EasyTask: we now have the algorithms for registering a new task and for deciding which task Marta tackles first designed, verified and with their edge cases checked. They are not sketches: they are specifications ready to implement.

With this we close module 1. You know what programming is, where the trade comes from, how languages are classified and chosen, you have a working environment with easytask.py on your disk and you know how to design an algorithm before writing it. It is exactly the kit needed to start writing real code.

And that is where we are heading now. In module 2 we leave the paper behind and return to Python with the piece that solves the limitation we have been carrying since the previous lesson — a program that only prints fixed text: variables, which let us store data and work with it. We will start with Variables and data types, where at last a task's title, assignee and priority will stop being written by hand inside a print and will become information the program handles.

© Copyright 2026. All rights reserved