Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 11 – Type hints and static type checking

TypedDict, Literal, NewType and enums

Type JSON-like dicts with TypedDict, allow only a few exact values with Literal and enums, keep IDs apart with NewType, and see what runs unchecked.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: TypedDict gives each key of a dictionary its own type · Literal types and enums allow only a few exact values · NewType keeps two kinds of int apart · 2 more sections. With a pass: 2 in-depth parts and 2 interview answers, about 12 minutes.

What you will learn

  • Type JSON-like dictionaries with TypedDict, including keys that may be missing
  • Limit a value to a few exact constants with a Literal type or an enum
  • Create distinct ID types with NewType so that swapped arguments are reported
  • Explain what each of the four checks before a run and what it leaves unchecked at run time
  • Choose between a Literal type and an enum for values that travel through JSON

Before you start

What you’ll learn · Before you start

What you will learn

  • Type JSON-like dictionaries with TypedDict, including keys that may be missing
  • Limit a value to a few exact constants with a Literal type or an enum
  • Create distinct ID types with NewType so that swapped arguments are reported
  • Explain what each of the four checks before a run and what it leaves unchecked at run time
  • Choose between a Literal type and an enum for values that travel through JSON

Before you start

On this page

An API sends an order as {"id": "A-101", "amount": 49900, "status": "paid"}, and typed as a plain dict[str, object], none of those keys means anything to a type checker: A program such as mypy or pyright that reads Python source without running it and reports every call, return and assignment that breaks a type hint, before the program runs. More in “Why type hints help (and what they don't do)”. Four tools let a checker understand such data more precisely. TypedDict: A class from typing that gives each key of a dictionary its own type, such as id: str and amount: int, for JSON-like records. The value is still a plain dict at run time, and Python checks none of its keys. gives every key of a dictionary its own type, as JSON-like records need. A Literal type: A type that allows only the exact values it lists: Literal["asc", "desc"] accepts those two strings and the checker refuses every other. At run time the value is the plain string, and nothing is checked. allows only a few exact values, such as "asc" and "desc". NewType: A separate name for an existing type, as in UserId = NewType("UserId", int). The checker keeps a UserId apart from a plain int and from other NewTypes, while at run time UserId(42) is the int 42 itself. makes a separate name for an existing type, so that a user ID and an order ID that are both integers cannot be swapped. Enums list named constants that still exist while the program runs.

mypy uses all four to report mistakes before the code runs. At run time, though, a TypedDict value is a plain dictionary, a Literal value a plain string and a NewType value a plain integer, so data from outside still needs checking. The recorded runs below show both sides.

TypedDict gives each key of a dictionary its own type

An API that returns orders sends the same keys every time: an id, an amount, a status and sometimes a coupon. As dict[str, object], every value is just an object to the checker, and every use of a value needs a cast or a check. A TypedDict class lists the keys and the type of each one, and NotRequired marks a key that may be missing. The class is written like a dataclass, but it describes a dictionary instead of creating a new kind of object.

An order typed with TypedDict, run by Python Python · orders/orders.py
"""An order as a shop's API returns it, typed with TypedDict and Literal."""
from typing import Literal, NotRequired, TypedDict


class Order(TypedDict):
    id: str
    amount: int  # in the currency's smallest unit, such as cents or paise
    status: Literal["created", "paid", "failed"]
    coupon: NotRequired[str]


def total(orders: list[Order]) -> int:
    return sum(order["amount"] for order in orders)


paid: Order = {"id": "A-101", "amount": 49_900, "status": "paid"}
typo: Order = {"id": "A-102", "amount": 9_900, "status": "payed"}
short: Order = {"id": "A-103", "status": "created"}

print(type(paid).__name__, paid)
print("required keys:", sorted(Order.__required_keys__))
print("optional keys:", sorted(Order.__optional_keys__))
print("the typo is stored:", typo["status"])
try:
    print(total([paid, typo, short]))
except KeyError as error:
    print("KeyError:", error)

Output

dict {'id': 'A-101', 'amount': 49900, 'status': 'paid'}
required keys: ['amount', 'id', 'status']
optional keys: ['coupon']
the typo is stored: payed
KeyError: 'amount'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 orders.py

  • The value is an ordinary dict. A TypedDict class creates no new kind of object, so building or receiving an order costs nothing extra and checks nothing.
  • The class keeps its lists of required and optional keys, which tools can read while the program runs.
  • The misspelt status "payed" is stored without complaint, and the order without an amount fails only when total() reads it, far from the line that made the mistake.

The checker reads the same file without running it; install the same version with pip install mypy==2.4.0 to repeat any of these checks:

mypy on orders.py Python · check/check_orders.py
from session import run

# The misspelt status and the order without an amount, as mypy reads orders.py.
run("""
mypy --version
mypy --strict orders.py
""", folder="../orders")

Output

$ mypy --version
mypy 2.4.0 (compiled: yes)
$ mypy --strict orders.py
orders.py:17: error: Incompatible types (expression has type "Literal['payed']", TypedDict item "status" has type "Literal['created', 'paid', 'failed']")  [typeddict-item]
orders.py:18: error: Missing key "amount" for TypedDict "Order"  [typeddict-item]
Found 2 errors in 1 file (checked 1 source file)
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_orders.py

mypy points at the two lines that made the mistakes, before the program has run once. Step through both readings of the file side by side:

Interactive · step by step Two ways to meet the mistakes of orders.py Step through the file as Python runs it, one line at a time, then as mypy reads it. Dashed arrows join each line Python ran to the report mypy gave for it.
Two ways to meet the mistakes of orders.py, step 8 of 8. Showing python3 orders.py, mypy --strict, Line 16: paid, Line 17: typo ("payed" stored, no error), Line 18: short (built without amount), Line 25: total() (KeyError: 'amount'), Line 17: the status (error [typeddict-item]), Line 18: a missing key (error: missing key). In focus: Line 17: typo, Line 17: the status, Line 18: short, Line 18: a missing key.same mistakesame mistakepython3 orders.pyruns it, line by linemypy --strictreads it, runs nothingLine 16: paidan ordinary dictLine 17: typo"payed" stored, no errorLine 18: shortbuilt without amountLine 25: total()KeyError: 'amount'Line 17: the statuserror [typeddict-item]Line 18: a missing keyerror: missing key

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Two readers. Python will run orders.py, and mypy --strict will read it without running it. The file has two mistakes, a misspelt status on line 17 and an order without an amount on line 18.
  2. An ordinary dict. Line 16 builds paid, annotated as an Order. Python makes the plain dict that the literal describes, which is why the run printed dict, and checks none of its keys.
  3. The typo is stored. Line 17 stores the status "payed". Nothing compares it with the three allowed strings, and the run printed: the typo is stored: payed.
  4. A short order. Line 18 builds an order with no amount key. Python builds it as happily as the others, because a TypedDict checks nothing when the program runs.
  5. The KeyError, far away. Line 25 calls total(), which reads the amount of every order, and the short one has none. The run stopped with KeyError 'amount', inside total(), far from the line that made the mistake.
  6. mypy reads line 17. mypy reads all 27 lines and runs none of them. The status of the order on line 17 is "payed", which is not one of the three Literal strings of the status key, so it reported typeddict-item.
  7. mypy reads line 18. The order on line 18 has no amount, and amount is a required key of Order, so mypy reported a missing key for that line too.
  8. The same two mistakes. Python never noticed the typo and met the missing key late, as a KeyError. mypy reported both lines with their numbers before the program ran once.

Checkpoint

Check: TypedDict keys and their types

  1. Question 1 of 2 paid: Order = {"id": "A-101", "amount": 49_900, "status": "paid"}, where Order is a TypedDict. What does type(paid).__name__ give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 'dict'

    Building or receiving a TypedDict value costs nothing extra and checks nothing: the value is an ordinary dict, and the class only keeps its lists of required and optional keys for tools to read.

  2. Question 2 of 2 short: Order = {"id": "A-103", "status": "created"} has no amount. Where did each tool notice?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: mypy on line 18, where the dictionary is written; Python only in total(), as a KeyError

    The checker points at the line that made the mistake, before the program has run once. At run time the same mistake shows up only where a key is read, far from where it was made.

Literal types and enums allow only a few exact values

A sort order, a mode or a status usually has a handful of valid values. A parameter typed Literal["asc", "desc"] accepts exactly those two strings and nothing else. Enums give the same protection with a class whose members exist at run time, and calling the class turns text into a member or refuses it.

A Literal sort order and a status enum Python · choices/choices.py
"""Two ways to allow only a few values: a Literal type and an enum."""
from enum import StrEnum
from typing import Literal, get_args

SortOrder = Literal["asc", "desc"]


def sort_prices(prices: list[int], order: SortOrder = "asc") -> list[int]:
    return sorted(prices, reverse=order == "desc")


class Status(StrEnum):
    CREATED = "created"
    PAID = "paid"
    FAILED = "failed"


def can_refund(status: Status) -> bool:
    return status is Status.PAID


print(sort_prices([300, 100, 200], "desc"))
print(sort_prices([300, 100, 200], "descending"))
print("allowed:", get_args(SortOrder))

print(can_refund(Status("paid")), can_refund("paid"))
try:
    Status("payed")
except ValueError as error:
    print("ValueError:", error)

Output

[300, 200, 100]
[100, 200, 300]
allowed: ('asc', 'desc')
True False
ValueError: 'payed' is not a valid Status

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 choices.py

mypy on choices.py Python · check/check_choices.py
from session import run

# The typo in the sort order and the plain string passed where a Status is expected.
run("""
mypy --version
mypy --strict choices.py
""", folder="../choices")

Output

$ mypy --version
mypy 2.4.0 (compiled: yes)
$ mypy --strict choices.py
choices.py:23: error: Argument 2 to "sort_prices" has incompatible type "Literal['descending']"; expected "Literal['asc', 'desc']"  [arg-type]
choices.py:26: error: Argument 1 to "can_refund" has incompatible type "str"; expected "Status"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_choices.py

The typo "descending" sorted the prices the wrong way without any error, because the function only compares its argument with "desc". The checker reports the call. can_refund("paid") printed False at run time: a member of a StrEnum is equal to its string, but is compares identity, and the checker reports the plain string as well. Status("payed") shows what enums add: the lookup raises ValueError while the program runs, which no annotation does.

Checkpoint

Check: Literal types and enums

  1. Question 1 of 2 sort_prices([300, 100, 200], "descending") is called, where order: Literal["asc", "desc"]. What happened?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Python sorted the prices in ascending order without an error, and mypy reported the argument

    A Literal type lists the exact values allowed. The checker refuses every other string, but at run time the value is a plain string and nothing compares it with the list.

  2. Question 2 of 2 can_refund("paid") printed False, although a StrEnum member is equal to its string. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: can_refund() compares with is, and the plain string "paid" is not the member Status.PAID

    Calling the enum class turns text into a member, or refuses it. Code that uses is must receive the member itself, which is what the annotation Status asks for and what mypy enforced.

NewType keeps two kinds of int apart

Here a user ID and an order ID, both plain ints, are swapped in one call:

A user ID and an order ID, swapped Python · ids/ids.py
"""Two kinds of ID that are both int at run time, kept apart by NewType."""
from typing import NewType

UserId = NewType("UserId", int)
OrderId = NewType("OrderId", int)


def cancel(user: UserId, order: OrderId) -> str:
    return f"user {user} cancels order {order}"


user = UserId(42)
order = OrderId(1001)
print(cancel(user, order))
print(cancel(order, user))
print(type(user).__name__, user == 42, user + 1)

Output

user 42 cancels order 1001
user 1001 cancels order 42
int True 43

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ids.py

mypy on ids.py Python · check/check_ids.py
from session import run

# Both arguments of the swapped call, as mypy reads ids.py.
run("""
mypy --version
mypy --strict ids.py
""", folder="../ids")

Output

$ mypy --version
mypy 2.4.0 (compiled: yes)
$ mypy --strict ids.py
ids.py:15: error: Argument 1 to "cancel" has incompatible type "OrderId"; expected "UserId"  [arg-type]
ids.py:15: error: Argument 2 to "cancel" has incompatible type "UserId"; expected "OrderId"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_ids.py

UserId(42) hands back the integer 42 itself, so the swapped call ran and printed nonsense. To mypy, though, a UserId and an OrderId are two different subtypes of int, so passing one where the other is expected is an error. NewType suits values that share a representation but must never mix: IDs, units, or text that has already been cleaned. Wrap a value once, where it enters the program, and the checker follows it from there.

Swap the IDs back

Press Edit on ids.py, change the second call to cancel(user, order) and run it: both lines now agree. Nothing in what Python printed before had pointed at the swapped call; only the checker’s report did.

Checkpoint

Check: NewType for values that must not mix

  1. Question 1 of 2 What is UserId(42) while the program runs, with UserId = NewType("UserId", int)?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The int 42 itself

    To Python a UserId is a plain int; to mypy it is a subtype of int that is kept apart from other subtypes, such as OrderId. That is why the swapped call ran and printed nonsense.

  2. Question 2 of 2 cancel(order, user) swaps a UserId and an OrderId. What did mypy report?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Two errors on line 15, one for each swapped argument

    NewType suits values that share a representation but must never mix. Wrap a value once, where it enters the program, and the checker follows it from there.

Only the enum checks values while the program runs

Before a run the checker reads all four kinds of annotation; during it, only one of them checks anything:

The checker reads all four kinds of annotation before a run; at run time only the enum still checks values.Your annotationsOrder (TypedDict) · SortOrder (Literal)UserId (NewType) · Status (enum)The type checkerreads every annotationreports mistakes before the runThe running programOrder is a dict · SortOrder is a strUserId is an int · Status is an enum memberStatus("payed")raises ValueErrorbefore it runswhile it runsonly the enum checks values

What each annotation becomes before and during a run

Text description of the diagram

The diagram starts with your annotations: an Order TypedDict, a SortOrder Literal type, a UserId NewType and a Status enum.

  • One arrow, labelled "before it runs", leads to the type checker, which reads every annotation and reports mistakes before the program runs.
  • A second arrow, labelled "while it runs", leads to the running program, where an Order is a plain dict, a SortOrder value is a plain str, a UserId is a plain int and a Status is an enum member.
  • From the running program, a last arrow labelled "only the enum checks values" leads to Status("payed") raising ValueError: of the four, only the enum refuses a wrong value while the program runs.
Tool What it is while the program runs Typical use
TypedDict A plain dict, and nothing is checked JSON objects and API responses
Literal The plain value, such as a str, and nothing is checked Modes, flags and sort orders
NewType The plain value of the base type, and nothing is checked IDs and units that must not mix
Enum An enum member: Status(value) refuses unknown values States with behaviour of their own

Annotations are read by the checker, and only the enum does any checking of its own while the program runs. Data from a file, a form or an API therefore still needs validation at the edge of the program, which a later lesson of this module builds.

Checkpoint

Check: What runs unchecked

  1. Question 1 of 2 Of TypedDict, Literal, NewType and enums, which one refuses a wrong value while the program runs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: An enum, because Status("payed") raises ValueError

    Annotations are read by the checker, and only the enum does any checking of its own while the program runs. Data from outside therefore still needs validation at the edge of the program.

  2. Question 2 of 2 An order arrives from an API, is parsed with json.loads() and is annotated as the TypedDict Order. What checks its keys when it arrives?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing, unless the program validates it itself

    A checker proves the program consistent with its annotations, but data from a file, a form or an API comes from outside the program, so it needs validation at the edge, which a later lesson of this module builds.

Check yourself

Practice that adapts to you

Practise this lesson

8 questions about 11 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 A value is annotated with a TypedDict class Order. What does type(value) give while the program runs?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: dict

    A TypedDict class describes a shape for the type checker. The value itself is the plain dictionary that the literal or json.loads() made, so type(value) is dict and no key or value is checked at run time.

  2. Question 2 of 8 Which problems of orders.py does mypy report before the program runs?

    Choose every answer that is right.

    Show the answer: Question 2 of 8

    Answer:

    • The order without an amount key
    • The status "payed", which is not one of the three allowed strings

    mypy checks every dictionary literal annotated as Order against the class: the status must be one of the Literal strings and every required key must be present. The type of the value at run time is not a mistake; it is what a TypedDict always is.

  3. Question 3 of 8 A parameter must accept only the strings "asc" and "desc". Which annotation tells the checker so?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: Literal["asc", "desc"]

    A Literal type lists the exact values that are allowed, and the checker rejects any other string, such as the typo "descending". NewType would make a separate kind of str, but it would still accept any text.

  4. Question 4 of 8 A status arrives as text from a form, and an unknown status must be refused while the program runs. Which tool does that by itself?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: An enum, by calling the class, Status(text)

    Enums give the protection of a Literal type with members that exist at run time, and calling the class is the check that turns outside text into a member or refuses it.

  5. Question 5 of 8 What does ids.py print?

    What does this program print? Choose one answer.

    """Two kinds of ID that are both int at run time, kept apart by NewType."""
    from typing import NewType
    
    UserId = NewType("UserId", int)
    OrderId = NewType("OrderId", int)
    
    
    def cancel(user: UserId, order: OrderId) -> str:
        return f"user {user} cancels order {order}"
    
    
    user = UserId(42)
    order = OrderId(1001)
    print(cancel(user, order))
    print(cancel(order, user))
    print(type(user).__name__, user == 42, user + 1)
    Show the answer: Question 5 of 8

    Answer: it prints

    user 42 cancels order 1001
    user 1001 cancels order 42
    int True 43

    UserId(42) returns the integer 42 itself, so the swapped call runs without any error and the result is an ordinary int. Only the type checker sees two different types; mypy reports both swapped arguments.

  6. Question 6 of 8 Which values are a good fit for NewType?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: A user ID and an order ID that are both ints but must never be swapped

    NewType suits values that share a representation but must never mix: IDs, units, or text that has already been cleaned. Wrap a value once, where it enters the program.

  7. Question 7 of 8 Which of these refuses the misspelt value "payed" while the program runs?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: Status("payed") on a StrEnum called Status

    Calling an enum class looks the value up among its members and raises ValueError when none matches. The Literal annotations are read only by the checker, and a NewType hands back whatever it is given.

  8. Question 8 of 8 A project uses NewType for its IDs but does not run a type checker in CI. A change swaps two IDs in a call. What notices?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: Nothing in Python, because at run time both IDs are plain ints; only a test with different IDs or a checker would

    The protection of TypedDict, Literal and NewType exists only when a checker reads the code, which is why these tools come with a checker in CI and validation at the edges of the program.

TypedDict in depth: optional, read-only and nested keys, checked by mypy Pro: opens with a Pro, Premium or Ultimate pass

A TypedDict describes more than a flat list of keys: keys that may be missing, keys that must not change, nested records and generic pages of results. This part runs each one through Python and through mypy, including the one mistake with optional keys that mypy lets through.

In this part:

  • The optional-key read that passes mypy and fails at run time, reproduced
  • Which TypedDicts fit where: extra keys, exact key types and ReadOnly, an invariant reproduced
  • Read-only keys, and why a TypedDict is not a dict[str, object]
  • Exercise: typed payments
  • Type a payment API's responses with TypedDict and Literal
    • Results of the sample tests

1 diagram · 6 runnable examples (Python) · 1 exercise with sample tests · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Literal, LiteralString and NewType at work: JSON, an injection drill and a measured cost Pro: opens with a Pro, Premium or Ultimate pass

Literal values, enums and NewTypes meet the outside world in JSON, in database queries and in hot loops. This part shows what each one costs and protects there, with a decision table for statuses that travel through APIs.

In this part:

  • Literal or enum for a status that travels through JSON, a trade-off table
  • LiteralString and query strings: an injection drill
  • Where NewType stops: arithmetic, isinstance() and wrapping again
  • What NewType costs at run time, measured

6 runnable examples (Python) · about 8 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher to mid · Warm-up

    When would you describe an API response with a TypedDict rather than a dataclass?

    A TypedDict describes a dictionary: the value stays the plain dict that json.loads() returned, nothing is copied, and code that reads it with ["key"] keeps working, while the checker knows the type of every key. A dataclass makes a new class: each object has to be built from the data, attributes replace keys, and the class can have methods, defaults, validation in __post_init__ and frozen instances. Choose a TypedDict when the data stays shaped like a dictionary, for example when it is passed on to another API or written back as JSON, or when existing code already reads dictionaries. Choose a dataclass when the record is a model of your own program with behaviour. Neither one checks the data at run time, apart from what __post_init__ does, so untrusted JSON needs validation either way.

  2. Mid to senior

    Your service types a payment API's response with a TypedDict, mypy passes in CI, and yet production logs show KeyError: 'coupon'. How can both be true, and what do you change so that this kind of failure cannot come back?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    An order status travels from a JSON API through your service into a database column. Would you type it as a Literal of strings or as an enum? Defend the choice, and say where the conversion between text and type happens.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • TypedDict gives each key of a dictionary its own type, and the value is still a plain dict at run time.
  • A Literal type allows only a few exact values, and an enum does the same with members that exist while the program runs.
  • NewType makes a separate type for the checker, so two kinds of ID that are both ints cannot be swapped unnoticed.
  • Only the enum refuses a wrong value while the program runs, so data from outside still needs validation.

Terms in this lesson

Literal type
A type that allows only the exact values it lists: Literal["asc", "desc"] accepts those two strings and the checker refuses every other. At run time the value is the plain string, and nothing is checked.
NewType
A separate name for an existing type, as in UserId = NewType("UserId", int). The checker keeps a UserId apart from a plain int and from other NewTypes, while at run time UserId(42) is the int 42 itself.
TypedDict
A class from typing that gives each key of a dictionary its own type, such as id: str and amount: int, for JSON-like records. The value is still a plain dict at run time, and Python checks none of its keys.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.